JVM 系列文章之 Full GC 和 Minor GC

pjmike_pj發表於2018-09-03

Full GC

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

RednaxelaFX大Major GC和Full GC的區別是什麼?觸發條件呢?- 知乎這個問題有關於 GC分類的回答:

針對 HotSpot VM的實現,它裡面的GC其實準確分類有兩種:

  • Partial GC(區域性 GC): 並不收集整個 GC 堆的模式
    • Young GC: 只收集young gen的GC,Young GC還有種說法就叫做 "Minor GC"
    • Old GC: 只收集old gen的GC。只有垃圾收集器CMS的concurrent collection 是這個模式
    • Mixed GC: 收集整個young gen 以及部分old gen的GC。只有垃圾收集器 G1有這個模式
  • Full GC: 收集整個堆,包括 新生代,老年代,永久代(在 JDK 1.8及以後,永久代被移除,換為metaspace 元空間)等所有部分的模式

Full GC的觸發條件

針對不同的垃圾收集器,Full GC的觸發條件可能不都一樣。按HotSpot VM的serial GC的實現來看,觸發條件是:

  • 當準備要觸發一次 young GC時,如果發現統計資料說之前 young GC的平均晉升大小比目前的 old gen剩餘的空間大,則不會觸發young GC而是轉為觸發 full GC (因為HotSpot VM的GC裡,除了垃圾回收器 CMS的concurrent collection 之外,其他能收集old gen的GC都會同時收集整個GC堆,包括young gen,所以不需要事先準備一次單獨的young GC)

  • 如果有永久代(perm gen),要在永久代分配空間但已經沒有足夠空間時,也要觸發一次 full GC

  • System.gc(),heap dump帶GC,其預設都是觸發 full GC.

HotSpot VM裡其他非併發GC的觸發條件複雜一些,不過大致原理與上面說的其實一樣。

在 Parallel Scavenge 收集器下,預設是在要觸發 full GC前先執行一次 young GC,並且兩次GC之間能讓應用程式稍微執行一小下,以期降低 full GC的暫停時間 (因為 young GC 會盡量清理了young gen的死物件,減少了 full GC的工作量)。控制這個行為的VM引數是: -XX:+ScavengeBeforeFullGC

併發GC的觸發條件就不一樣,以 CMS GC為例,它主要是定時去檢查old gen的使用量,但使用量超過了觸發比例就會啟動一次 CMS GC,對old gen做併發收集

Minor GC

Minor GC 是俗稱,新生代(新生代分為一個 Eden區和兩個Survivor區)的垃圾收集叫做 Minor GC。在 Oracle 高階研究員鄭雨迪的極客時間專欄 《深入拆解Java虛擬機器》中也談到 Minor GC,內容如下:

觸發條件

當 Eden 區的空間耗盡了怎麼辦?這個時候 Java虛擬機器便會觸發一次 Minor GC來收集新生代的垃圾,存活下來的物件,則會被送到 Survivor區。

簡單說就是當新生代的Eden區滿的時候觸發 Minor GC

Minor GC的過程

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

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

注意: from與to只是兩個指標,它們變動的,to指標指向的Survivor區是空的

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

Java虛擬機器會記錄 Survivor區中的物件一共被來回複製了幾次。如果一個物件被複制的次數為 15 (對應虛擬機器引數 -XX:+MaxTenuringThreshold),那麼該物件將被晉升為至老年代,(至於為什麼是 15次,原因是 HotSpot會在物件頭的中的標記欄位裡記錄年齡,分配到的空間只有4位,所以最多隻能記錄到15)。另外,如果單個 Survivor 區已經被佔用了 50% (對應虛擬機器引數: -XX:TargetSurvivorRatio),那麼較高複製次數的物件也會被晉升至老年代。

當Survivor區的部分物件晉升到老年代後,老年代的佔用量通常會升高。

注意

在Minor GC過程中,Survivor 可能不足以容納Eden和另一個Survivor中的存活物件。如果Survivor中的存活物件溢位,多餘的物件將被移到老年代,這稱為過早提升(Premature Promotion),這會導致老年代中短期存活物件的增長,可能會引發嚴重的效能問題。再進一步說,在Minor GC過程中,如果老年代滿了而無法容納更多的物件,Minor GC 之後通常就會進行Full GC,這將導致遍歷整個Java堆,這稱為提升失敗(Promotion Failure)。至於解決辦法,這就涉及到對應用程式的調優問題了,這裡就不敘述了,如有興趣,請自行查閱相關資料

Minor GC的問題與卡表分析

Minor GC存在一個問題就是,老年代的物件可能引用新生代的物件,在標記存活物件的時候,就需要掃描老年代的物件,如果該物件擁有對新生代物件的引用,那麼這個引用也會被作為 GC Roots。這相當於就做了全堆掃描

JVM如何避免Minor GC掃描全堆

HotSpot 給出的解決方案是 一項叫做 卡表 的技術。如下圖所示:

card table

卡表的具體策略是將老年代的空間分成大小為 512B的若干張卡,並且維護一個卡表,卡表本省是位元組陣列,陣列中的每個元素對應著一張卡,其實就是一個標識位,這個標識位代表對應的卡是否可能存有指向新生代物件的引用,如果可能存在,那麼我們認為這張卡是髒的,即髒卡。如上圖所示,卡表3被標記為髒。

在進行Minor GC的時候,我們便可以不用掃描整個老年代,而是在卡表中尋找髒卡,並將髒卡中的老年代指向新生代的引用加入到 Minor GC的GC Roots裡,當完成所有髒卡的掃描之後,Java 虛擬機器便會將所有髒卡的標識位清零。這樣虛擬機器以空間換時間,避免了全表掃描

關於 Major GC的說明

除了Full GC和Minor GC外,還有一種說法叫做 "Major GC":

Major GC通常是跟full GC是等價的,收集整個GC堆,但因為 HotSpot VM發展這麼多年,外界對各種名詞的解讀已經完全混亂了,當有人說"Major GC"的時候一定要問清楚他想要指的是上面的 full GC還是 old GC

以上是 R大關於 Major GC的說法,比較權威的。在網上還流行著另外一種說法就是 Major GC是對老年代的垃圾回收

小結

以上的內容基本上是從 R大的知乎回答與極客時間專欄 《深入拆解Java虛擬機器》總結來的,在總結過程中,也算是對 Full GC 與 Minor GC 有了一個基本的認識。

取之網路,再回饋之網路,本人對於JVM是渣渣級選手,如有錯誤之處,歡迎指教

參考資料 & 鳴謝

相關文章