本文主要基于周志明老師的《深入了解Java虛拟機》。
文章目錄
-
- Garbage Collection
-
- 哪些記憶體需要被回收?
-
- 引用計數算法 Reference Count
- 可達性分析算法 Root Searching
- 三色标記清除算法 Tri-color Marking
-
- 增量更新 Incremental Update
- 原始快照 Snapshot At The Beginning
- 垃圾回收算法
-
- 标記清除 Mark-Sweep
- 拷貝 Copying
- 标記壓縮 Mark-Compact
- 标記-清除-壓縮 Mark-Sweep-Compat
- 基于分代的垃圾回收
-
- JVM記憶體分代模型
- HotSpot中預設配置
- MinorGc、MajorGC、YGC、FGC
- 卡表 Card Table
- 垃圾收集器
-
- Serial
- Serial Old
- Parallel Old
- Parallel Scavenge
- ParNew
- CMS
- G1
- Shenandoah
- Epsilon
- ZGC
- JVM指定垃圾收集器
- 拓展閱讀
Garbage Collection
自Java引入垃圾回收以來,垃圾回收器的發展就未停止過。在JDK11種引入了ZGC,在JDK12種又引入了Shenandoah。雖然新的垃圾回收器不斷地湧現,但是垃圾回收的基礎算法變化并不大。簡單來說,回收算法主要有複制、标記清除、标記壓縮。
垃圾回收可以分為三個問題:哪些記憶體需要被回收?什麼時候去回收?如何回收?
哪些記憶體需要被回收?
簡單的說,沒有任何引用指向的一個對象或者循環引用的多個對象。需要被回收。目前垃圾回收算法主要有兩類,引用計數法和可達性分析算法。JVM的垃圾回收采用了可達性分析法。
引用計數算法 Reference Count
在堆記憶體中配置設定對象時,會為對象配置設定一段額外空間。這段空間用于記錄引用計數,當對象增加了一個新的引用,則将增加計數器的值。如果一個引用關系失效,則減少計數器的值。當一個對象的計數器的值變成0,則說明該對象已經被廢棄,處于不活躍狀态,可以被回收。引用計數法存在無法回收循環引用的問題,并且并發計數需要上鎖效率低。Python語言中使用的便是引用計數法。
可達性分析算法 Root Searching
通過一組根對象根據引用鍊向下标記,沒有被标記到的對象被稱為不可達,證明此對象可被回收。
三色标記清除算法 Tri-color Marking
三色标記清除算法
https://www.bilibili.com/video/BV1KU4y1Y7cu
增量更新&SATB
https://www.bilibili.com/video/BV1Uz4y1S798
三色标記清除算法中用三種顔色來區分不同的記憶體節點。
- 白 沒有周遊到的節點。
- 灰 自己标記完成,還沒來得及标記子節點。
- 黑 自己标記完成,子節點都标記完成。
并發标記導緻的"對象消失"問題,當且僅當以下兩個條件同時滿足時,會産生“對象消失”的問題,即原來應該是黑色的對象被誤标成了白色。
- 指派器插入一條或多條從黑色對象到白色對象的新引用。
- 指派器删除了全部從灰色對象到該白色對象的直接或間接引用。
對此分别有兩種解決方案,增量标記(Incremental Update)和原始快照(SATB)。
增量更新 Incremental Update
在JVM中,CMS采用的是增量标記。
增量更新破壞了第一個條件,可以簡化了解為把黑色重新标記為灰色,下次重新掃描子節點。
原始快照 Snapshot At The Beginning
在JVM中,G1采用原始快照。
原始快照破壞了第二個條件,可以簡化了解為無論引用關系删除與否,都會按照剛開始掃描那一刻的對象圖快照來進行搜尋。
垃圾回收算法
标記清除 Mark-Sweep
标記清除
位置不連續 産生碎片 效率偏低(兩遍掃描)
拷貝 Copying
拷貝
沒有碎片,浪費空間
标記壓縮 Mark-Compact
标記壓縮
沒有碎片,效率偏低(兩遍掃描,指針需要調整)
标記-清除-壓縮 Mark-Sweep-Compat
多次GC後才壓縮,是标記清除和标記壓縮的結合。
基于分代的垃圾回收
基于分代的垃圾回收的本質就是分代假說。弱分代假說(weak generational hypothesis)的含義是:大多數對象都在年輕時死亡。強分代假說(strong generational hypothesis)的含義是:越老的對象越不容易死亡。
更多内容請參考《垃圾回收算法手冊:自動記憶體管理的藝術》。
JVM記憶體分代模型
HotSpot虛拟機的垃圾收集器
除Epsilon、ZGC、Shenandoah之外的垃圾收集器都是使用邏輯分代模型。G1采用邏輯分代實體不分代,是以可以同時用于傳統意義上的年輕代和老年代。除此之外的垃圾收集器不僅邏輯分代還實體分代。
HotSpot中預設配置
新生代 :老年代 = 1 : 3
新生代 = Eden + Suvivor0 + Suvivor1
Eden :Suvivor0 :Suvivor1 = 8 :1 :1
MinorGc、MajorGC、YGC、FGC
YGC = Young GC = MinorGC
FGC = Full GC = MajorGC
卡表 Card Table
垃圾回收跨代引用/卡表/寫屏障
https://www.bilibili.com/video/BV1Jy4y1p7t8
執行YGC時,需要掃描整個老年代,效率非常低,是以JVM設計了CardTable。如果一個老年代CardTable中有對象指向年輕代,就将它設為Dirty,下次掃描時,隻需要掃描Dirty Card。
在資料結構上,Card Table用BitMap來實作。
垃圾收集器
Serial
Serial是一個stop-the-world,基于拷貝算法的單GC線程年輕代垃圾收集器。
Serial Old
Serial Old是一個stop-the-world,基于mark-sweep-compact(MSC)算法的單GC線程老年代垃圾收集器。
Parallel Old
Parallel Old是一個stop-the-world,基于壓縮算法的多GC線程老年代垃圾收集器。
Parallel Scavenge
Paralledl Scavenge是一個stop-the-world,基于拷貝算法的多GC線程年輕代垃圾收集器。
ParNew
在ParNew在Paralledl Scavenge之後誕生,是以叫Parallel New。
Parallel Old是一個stop-the-world,基于壓縮算法的多GC線程老年代垃圾收集器。
他和Paralledl Scavenge的差別是它做了增強使他可以和CMS配合使用。
CMS
Concurrent Mark Sweep
算法:三色标記 + Incremental Update
老年代
垃圾回收和應用程式同時運作,降低STW的時間(200ms)
CMS問題比較多,是以現在沒有一個版本預設是CMS,隻能手工指定
一個大多數并發、低停頓的收集器
CMS既然是MarkSweep,就一定會有碎片化的問題,碎片到達一定程度,CMS的老年代配置設定對象配置設定不下的時候,使用SerialOld 進行老年代回收
4階段
初始标記 initial mark (STW)
并發标記 concurrent mark
重新标記 remark (STW)
并發清除 concurrent sweep
G1
算法:三色标記 + SATB(Snapshot-At-The-Beginnin)
Shenandoah
算法:ColoredPointers + WriteBarrier
Epsilon
Epsilon不回收記憶體,用來做測試的。
ZGC
算法:ColoredPointers + LoadBarrier
JVM指定垃圾收集器
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
-XX:+UseParNewGC = ParNew + SerialOld(某些版本已廢棄)
-XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8預設)
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-XX:+UseG1GC = G1
拓展閱讀
《垃圾回收算法手冊:自動記憶體管理的藝術》.https://book.douban.com/subject/26740958
《新一代垃圾回收器ZGC設計與實作》.https://book.douban.com/subject/34812818/