[轉帖]內存分析之GCViewer詳細解讀

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 人正在系統學習中
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章