文章目錄
JVM Garbage First 垃圾回收器
定義:Garbage First
- 2004論文發佈
- 2009 JDK 6u14 體驗
- 2012 JDK 7u4 官方支持
- 2017 JDK 9 默認
使用場景:
- 同時注重吞吐量(Throughput)和低延遲(Low latency),默認的暫停目標是200 ms
- 超大堆內存,會將堆劃分爲多個大小相等的Region
- 整體上是標記+整理算法,兩個區域之間是複製算法
相關JVM參數
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
:堆內存Region大小
-XX:MaxGCPauseMillis=time
:暫停目標時間
1、Young Collection
會發生 STW(Stop The World)
- 初始階段,新生成的對象會進入伊甸園
- 在發生垃圾回收時,伊旬園中的未被回收的對象會進入倖存區
- 再次發生垃圾回收時,伊旬園中的未被回收的新對象會複製到一個新的倖存區(Region塊),並將舊倖存區中達到指定閾值的對象複製到老年代,沒有被回收且沒有達到閾值的對象複製到新的倖存區
2、Young Collection + CM
- 在 Young GC 會進行 GC Root 的初始標記
- 老年代佔用堆空間比例達到閾值時,進行併發標記(不會 STW),由下面的 JVM 參數決定
-XX:InitiatingHeapOccupancyPercent=percent (默認爲45%)
3、Mixed Collection
會對伊甸園、倖存區、老年代進行全面的垃圾回收
- 最終標記(Remark)會發生 STW
- 拷貝存貨(Evacuation)會發生 STW
-XX:MaxGCPauseMillis=ms
在此階段伊甸園中的未被回收的對象會複製到倖存區中,同時倖存區中也會進行垃圾回收,將倖存區中未被回收的對象複製到新的倖存區中,壽命到達指定閾值的對象複製到老年代中。在回收老年代時也是同樣使用複製算法,並根據設置的最大暫停時間,優先回收其中回收價值最高的部分老年代中的對象。
4、Full GC
- Serial GC
- 新生代內存不足發生的垃圾回收:Minor GC
- 老年代內存不足發生的垃圾回收:Full GC
- Parallel GC
- 新生代內存不足發生的垃圾回收:Minor GC
- 老年代內存不足發生的垃圾回收:Full GC
- CMS
- 新生代內存不足發生的垃圾回收:Minor GC
- 到老年代所佔空間達到設置的閾值時,會觸發併發標記後混合回收,當併發垃圾回收失敗時就會發生Full GC
- G1
- 新生代內存不足發生的垃圾回收:Minor GC
- 到老年代所佔空間達到設置的閾值時,會觸發併發標記後混合回收,此時,垃圾回收的速度小於垃圾產生的速度時,就會發生Full GC
5、Young Collection 跨帶引用
-
新生代回收的跨代引用(老年代引用新生代)
-
在新生代中進行垃圾回收時,要找到根對象(GC Root)進行可達性分析,而部分新生代中對象的根對象(GC Root)時在老年代中的。而遍歷老年代查找根對象效率較低,因此使用卡表(Card Table)將老年代細分爲卡(Card)組成的卡表(Card Table),當老年代中的對象引用了新生代中的對象,則會把老年代中對應的卡(Card)標記爲髒卡(Dirty Card)。此時,在老年代中的對象引用新生代中的對象後,進行垃圾回收時就不必遍歷整個老年代,提升垃圾回收的效率。
-
在新生代對象中有一個Remembered Set,記錄着老年代中引用此新生代對象的髒卡(Dirty Card),在進行垃圾回收時就可以通過Remembered Set找到對應的髒卡(Dirty Card),然後通過髒卡(Dirty Card)遍歷根對象(GC Root)。
-
在對象引用變更時通過寫後屏障(Post-Write Barrier)和髒卡隊列(Dirty Card Queue)
-
Remembered Set通過Concurrent Refinement Threads更新
6、Remark
在垃圾回收器進行併發標記階段時,會在可以回收的對象上加入寫屏障(Pre-Write Barrier),以防對象在標記後發生引用變化後依舊清除對象。
當添加寫屏障(Pre-Write Barrier)的對象發生引用變化之後,寫屏障會將對象加入satb_mark_queue,在重新標記(Remark)時(會發生 STW)就會重新判斷satb_mark_queue中的對象是否可以被清除。
7、JDK 8u20 字符串去重
- 優點:節省大量內存
- 缺點:略微多佔用了cpu時間,新生代回收時間略微增加
-XX:UseStringDeduplication
String s1 = new String("hello"); //char[]{'h', 'e', 'l', 'l', 'o'}
String s2 = new String("hello"); //char[]{'h', 'e', 'l', 'l', 'o'}
- 將所有新分配的字符串放入一個隊列
- 當新生代回收時,G1併發檢查是否有字符串重複
- 如果它們值一樣,讓它們引用同一個char[]
注意:此字符串去重與
String.intern()
不同,String.intern()
關注的是字符串對象而字符串去重關注的是char[]
在JVM內部,使用了不同的字符串表。
8、JDK 8u40 併發標記類卸載
所有對象都經過併發標記後,就知道哪些類不再被使用,當一個類加載器的所有類都不在使用,則卸載它所加載的所有類
-XX:+ClassUnloadingWithConcurrentMark // 默認啓用
9、JDK 8u60 回收巨型對象
- 一個對象大於Regin的一半時,稱之爲巨型對象
- G1不會對巨型對象進行拷貝
- 回收時被優先考慮
- G1會跟蹤老年代所有incoming引用,這樣老年代incoming引用爲0的巨型對象就可以在新生代垃圾回收時處理掉
10、JDK 9 併發標記起始時間調整
- 併發標記必須在堆空間佔滿前完成,否則退化爲Full GC
- JDK 9 之前需要使用
-XX:InitiatingHeapOccupancyPercent
- JDK 9 可以動態調整
-XX:InitiatingHeapOccupancyPercent
用來設置初始值- 進行數據採樣並動態調整
- 總會添加一個安全的空檔空間