[轉帖]記憶體分析之GCViewer詳細解讀

济南小老虎發表於2024-04-13

文章目錄

  • GCViewer詳細解讀
    • 一,Chart詳解
    • 二,Event detail
    • 三,Summary
    • 四,Pause
    • 五,相關概念
    • 5.1 GC
      • 5.1.1 Full GC
      • 5.1.2 Minor GC
    • 5.2 垃圾收集器
      • 5.2.1 序列收集器(Serial)
      • 5.2.2 **ParNew收集器**
      • 5.2.3 **Parallel Scavenge**收集器
      • **5.2.4 CMS收集器(Concurrent Mark Sweep)**
      • **5.2.5 G1收集器(Garbage First)**
    • 5.3 Metaspace
    • 5.4 Compressed Class Space

GCViewer詳細解讀

一,Chart詳解

  • Full GC Lines:完整回收,用於回收整個堆空間中的無用物件,包括年輕代和老年代中的物件。與Minor GC(年輕代垃圾回收)不同的是,Full GC是針對整個堆空間的操作。這樣的黑線越少越好。
  • Inc GC Lines:增量式GC。增量式垃圾回收(Incremental GC)是一種透過逐漸推進垃圾回收來控制mutator 最大暫停時間的方法。 通常的GC 處理很繁重,一旦GC 開始執行,過程中mutator會完全停止。(停止型 GC)。增量式垃圾回收是將 GC 和 mutator 一點點交替執行的手法。
  • GC Times Line:GC花費的時間,折線圖形式
  • GC Times Rectangles:GC持續時間區域,柱狀形式
  • Total Heap:整個堆的大小
  • Tenured Generation:老年代堆空間大小
  • Young Generation:年輕代堆空間大小
  • Used Heap:堆使用量,反映已使用堆佔用記憶體變化情況
  • Used Tenured Heap:已使用老年代堆空間大小,反映老年代堆空間佔用記憶體變化情況
  • Used yound Heap:已使用年輕代堆空間大小,反映年輕代堆空間佔用記憶體變化情況
  • Initial mark level:cms或G1垃圾回收演算法初始標記事件
  • Concurrent collections:cms或G1垃圾回收併發收集週期

二,Event detail

該標籤中,能夠看到日誌中全部重要的GC暫停彙總:

  • Gc pauses:普通GC停頓
  • Full gc pauses:Full GC停頓次數
  • VM operation overhead:VM操作開銷
  • Concurrent GCs:併發執行數

Total pause = Gc pauses + Full gc pauses + VM operation overhead + Concurrent GCs

三,Summary

  • Total heap(usage / alloc.max):總大小和使用情況

  • Max heap after full GC:Full GC後的堆記憶體大小

  • Total Time:GC總耗時

  • Accumulated Pauses:GC過程中暫停總時長

  • Throughput:吞吐量百分比,顯示了有效工作的時間比例, 剩下的部分就是GC的消耗,一般要求吞吐量至少為90%

  • Number of GC pauses:GC暫停的次數

  • Number of full GC pause:Full GC暫停的次數

如果Throughput比例過低,則意味著CPU有太多時間用在GC上面非常明顯系統所面臨的情況非常糟糕:寶貴的CPU時間沒實用於執行實際工作, 而是在試圖清理垃圾,可能需要最佳化。

四,Pause

選項卡Pause的內容大部分在【Event detail】中都有出現,這裡只列出一組數值即可

  • Avg pause:平均暫停時間
  • Avg pause interval:平均暫停時間的間隔時間
  • Min/max pause interval:最小/大暫停時間的間隔時間

如果Full GC平均的暫停時間很長(大於1s),或者平均暫停間隔時間非常短(小於10s),說明GC回收是有問題的,可能需要最佳化。

五,相關概念

5.1 GC

5.1.1 Full GC

Full GC 就是收集整個堆,包括新生代,老年代,永久代(在JDK 1.8及以後,永久代會被移除,換為metaspace)等收集所有部分的模式。

Full GC觸發條件

1、老年代空間不夠。當老年代剩餘的可用空間不足以存放新建立的物件時,JVM會觸發Full GC,回收無用的物件來獲得更多的空間。

2、System.gc()請求。當呼叫System.gc()方法顯式地要求進行垃圾回收時,JVM會優先執行Full GC操作。

3、Perm區滿。當Perm區的記憶體空間不足以載入新的Class檔案時,JVM會觸發Full GC。

Full GC 頻繁觸發原因

1、堆空間配置不足。如果初始堆空間的大小設定過小,容易導致Full GC的頻繁觸發。

2、程式中存在大量的臨時物件。如果程式中頻繁地建立大量的臨時物件,會導致堆空間快速被填滿,從而引發Full GC。

3、程式有記憶體洩漏。如果程式中存在記憶體洩漏或不合理的物件引用,那麼這些物件會一直駐留在堆空間中,從而佔用大量的記憶體,最終會導致Full GC的頻繁觸發。

5.1.2 Minor GC

Minor GC ,新生代(新生代分為一個 Eden區和兩個Survivor區)的垃圾收集叫做 Minor GC。

Minor GC觸發條件

新生代的Eden區滿的時候觸發

Minor GC過程

新生代共有 兩個 Survivor區,分別用 from 和 to來指代。其中 to 指向的Survivor區是空的。

當發生 Minor GC時,Eden 區和 from 指向的 Survivor 區中的存活物件會被複制(此處採用標記 - 複製演算法)到 to 指向的 Survivor區中,然後交換 from 和 to指標,以保證下一次 Minor GC時,to 指向的 Survivor區還是空的。

Survivor區物件晉升位老年代物件的條件

如果一個物件被複制的次數為 15 (對應虛擬機器引數 -XX:+MaxTenuringThreshold),那麼該物件將被晉升為至老年代,(至於為什麼是 15次,原因是 HotSpot會在物件頭的中的標記欄位裡記錄年齡,分配到的空間只有4位,所以最多隻能記錄到15)。

另外,如果單個 Survivor 區已經被佔用了 50% (對應虛擬機器引數: -XX:TargetSurvivorRatio),那麼較高複製次數的物件也會被晉升至老年代。

5.2 垃圾收集器

5.2.1 序列收集器(Serial)

GC日誌標識:DefNew

是最基本、發展歷史最悠久的收集器,曾經(在JDK 1.3.1之前)是虛擬機器新生代收集的唯一選擇。這個收集器是一個單執行緒的收集器,但它的“單執行緒”的意義並不僅僅說明它只會使用一個CPU或一條收集執行緒去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作執行緒,直到它收集結束。

GC日誌標識:Tenured

是Serial收集器的老年代版本,它同樣是一個單執行緒收集器,使用“標記-整理”演算法。這個收集器的主要意義也是在於給Client模式下的虛擬機器使用。

如果在Server模式下,那麼它主要還有兩大用途:一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge 收集器搭配使用[1],另一種用途就是作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。

5.2.2 ParNew收集器

GC日誌標識:ParNew

除了使用多條執行緒進行垃圾收集之外,其餘行為包括Serial收集器可用的所有控制引數、收集演算法、Stop The World、物件分配規則、回收策略等都與Serial收集器完全一樣,在實現上,這兩種收集器也共用了相當多的程式碼。

5.2.3 Parallel Scavenge收集器

GC日誌標識:PsYoungGen

新生代收集器,它也是使用複製演算法的收集器,又是並行的多執行緒收集器。

它的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是儘可能地縮短垃圾收集時使用者執行緒的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput)。

吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)

虛擬機器總共執行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

由於與吞吐量關係密切,Parallel Scavenge收集器也經常稱為“吞吐量優先”收集器。

GC日誌標識:PsOldGen

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多執行緒和“標記-整理”演算法。這個收集器是在JDK 1.6中才開始提供的,在此之前,新生代的Parallel Scavenge收集器一直處於比較尷尬的狀態。原因是,如果新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器外別無選擇。

直到Parallel Old收集器出現後,“吞吐量優先”收集器終於有了比較名副其實的應用組合,在注重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old。

5.2.4 CMS收集器(Concurrent Mark Sweep)

GC日誌標識:以CMS開頭

為短暫停應用時間為目標而設計的,是基於標記-清除演算法實現,整個過程分為4個步驟,包括:

  1. 初始標記(Initial Mark)
  2. 併發標記(Concurrent Mark)
  3. 重新標記(Remark)
  4. 併發清除(Concurrent Sweep)

其中,初始標記、重新標記這兩個步驟仍然需要暫停應用執行緒。

初始標記只是標記一下GC Roots能直接關聯到的物件,速度很快,併發標記階段是標記可回收物件。

重新標記階段則是為了修正併發標記期間因使用者程式繼續運作導致標記產生變動的那一部分物件的標記記錄,這個階段暫停時間比初始標記階段稍長一點,但遠比並發標記時間段短。

由於整個過程中消耗最長的併發標記和併發清除過程收集器執行緒都可以與使用者執行緒一起工作,所以,CMS收集器記憶體回收與使用者一起併發執行的,大大減少了暫停時間。

5.2.5 G1收集器(Garbage First)

G1收集器是Java虛擬機器的垃圾收集器理論進一步發展的產物,它與前面的CMS收集器相比有兩個顯著的改進:

一是G1收集器是基於“標記-整理”演算法實現的收集器,也就是說它不會產生空間碎片,這對於長時間執行的應用系統來說非常重要。

二是它可以非常精確地控制停頓,既能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,具備了一些實時Java(RTSJ)的垃圾收集器的特徵。

在G1中,堆被分成一塊塊大小相等的heap region,一般有2000多塊,這些region在邏輯上是連續的。每塊region都可以作為獨立的新生代,倖存區和老年代。

GC時G1的執行方式與CMS方式類似,會有一個全域性併發標記(concurrent global marking phase)的過程,去確定堆裡物件的的存活情況。併發標記完成之後,G1會優先回收垃圾多的region區,最大化釋放出空間。這是為什麼這種垃圾回收方式叫G1的原因(Garbage-First)。

G1收集器工作工程分為4個步驟,包括:

  1. 初始標記(Initial Mark)
  2. 併發標記(Concurrent Mark)
  3. 最終標記(Final Mark)
  4. 篩選回收(Live Data Counting and Evacuation)

初始標記與CMS一樣,標記一下GC Roots能直接關聯到的物件。

併發標記從GC Root開始標記存活物件,這個階段耗時比較長,但也可以與應用執行緒併發執行。

最終標記也是為了修正在併發標記期間因使用者程式繼續運作而導致標記產生變化的那一部分標記記錄。

最後在篩選回收階段對各個Region回收價值和成本進行排序,根據使用者所期望的GC暫停時間來執行回收。

5.3 Metaspace

Metaspace 區域位於堆外,所以它的最大記憶體大小取決於系統記憶體,而不是堆大小,我們可以指定 MaxMetaspaceSize 引數來限定它的最大記憶體。

Metaspace 是用來存放 class metadata 的,class metadata 用於記錄一個 Java 類在 JVM 中的資訊,包括但不限於JVM class file format的執行時資料:

  1. Klass 結構,這個非常重要,把它理解為一個 Java 類在虛擬機器內部的表示
  2. method metadata,包括方法的位元組碼、區域性變數表、異常表、引數資訊等
  3. 常量池
  4. 註解
  5. 方法計數器,記錄方法被執行的次數,用來輔助 JIT 決策
  6. 其他

雖然每個 Java 類都關聯了一個 java.lang.Class 的例項,而且它是一個貯存在堆中的 Java 物件。但是類的 class metadata 不是一個 Java 物件,它不在堆中,而是在 Metaspace 中。

何時分配Metaspace

當一個類被載入時,它的類載入器會負責在 Metaspace 中分配空間用於存放這個類的後設資料。

何時回收Metaspace

分配給一個類的空間,是歸屬於這個類的類載入器的,只有當這個類載入器解除安裝的時候,這個空間才會被釋放。

所以,只有當這個類載入器載入的所有類都沒有存活的物件,並且沒有到達這些類和類載入器的引用時,相應的 Metaspace 空間才會被 GC 釋放。

釋放 Metaspace 的空間,並不意味著將這部分空間還給系統記憶體,這部分空間通常會被 JVM 保留下來。

這部分被保留的空間有多大,取決於 Metaspace 的碎片化程度。另外,Metaspace 中有一部分割槽域 Compressed Class Space 是一定不會還給作業系統的。

VM引數

  • -XX:MaxMetaspaceSize:Metaspace 總空間的最大允許使用記憶體,預設是不限制
  • -XX:CompressedClassSpaceSize:Metaspace 中的 Compressed Class Space 的最大允許記憶體,預設值是 1G,這部分會在 JVM 啟動的時候向作業系統申請 1G 的虛擬地址對映,但不是真的就用了作業系統的 1G 記憶體。

何時觸發GC

Metaspace 只在 GC 執行並且解除安裝類載入器的時候才會釋放空間。當然,在某些時候,需要主動觸發 GC 來回收一些沒用的 class metadata,即使這個時候對於堆空間來說,還達不到 GC 的條件。

Metaspace 可能在兩種情況下觸發 GC:

1、分配空間時:虛擬機器維護了一個閾值,如果 Metaspace 的空間大小超過了這個閾值,那麼在新的空間分配申請時,虛擬機器首先會透過收集可以解除安裝的類載入器來達到複用空間的目的,而不是擴大 Metaspace 的空間,這個時候會觸發 GC。這個閾值會上下調整,和 Metaspace 已經佔用的作業系統記憶體保持一個距離。

2、碰到 Metaspace OOM:Metaspace 的總使用空間達到了 MaxMetaspaceSize 設定的閾值,或者 Compressed Class Space 被使用光了,如果這次 GC 真的透過解除安裝類載入器騰出了很多的空間,這很好,否則的話,會進入一個糟糕的 GC 週期,即使有足夠的堆記憶體。

5.4 Compressed Class Space

在Java8以前,有一個選項是UseCompressedOops。所謂OOPS是指“ordinary object pointers“,就是原始指標。Java Runtime可以用這個指標直接訪問指標對應的記憶體,做相應的操作(比如發起GC時做copy and sweep)。

64bit的JVM出現後,OOPS的尺寸也變成了64bit,比之前的大了一倍。這會引入效能損耗——佔的記憶體翻倍,並且同尺寸的CPU Cache要少存一倍的OOPS。

於是就有了UseCompressedOops這個選項。開啟後,OOPS變成了32bit。但32bit的base是8,所以能引用的空間是32GB——這遠大於目前經常給jvm程序記憶體分配的空間。

在 64 位平臺上,HotSpot 使用了兩個壓縮最佳化技術,Compressed Object Pointers (“CompressedOops”) 和 Compressed Class Pointers

壓縮指標,指的是在 64 位的機器上,使用 32 位的指標來訪問資料(堆中的物件或 Metaspace 中的後設資料)的一種方式。

這樣有很多的好處,比如 32 位的指標佔用更小的記憶體,可以更好地使用快取,在有些平臺,還可以使用到更多的暫存器。

當然,在 64 位的機器中,最終還是需要一個 64 位的地址來訪問資料的,所以這個 32 位的值是相對於一個基準地址的值。

到了Java8,永久代被幹掉了,有了“meta space”的概念,儲存jvm中的後設資料,包括byte code,class等資訊。Java8在UseCompressedOops之外,額外增加了一個新選項叫做UseCompressedClassPointer。這個選項開啟後,class資訊中的指標也用32bit的Compressed版本。而這些指標指向的空間被稱作“Compressed Class Space”。預設大小是1G,但可以透過“CompressedClassSpaceSize”調整。

如果java程式引用了太多的包,有可能會造成這個空間不夠用,於是會看到

java.lang.OutOfMemoryError: Compressed class space

    這時,一般調大CompreseedClassSpaceSize就可以了。

    文章知識點與官方知識檔案匹配,可進一步學習相關知識
    演算法技能樹首頁概覽60079 人正在系統學習中

    相關文章