文章目錄
- 0. 分代收集理論
- 分代假說
- 分代GC定義
- 1. 垃圾回收算法
- 1.1 標記清除(Mark-Sweep)算法
- 優點
- 缺點
- 1.2 標記復制算法
- 優點
- 缺點
- 為什么是8:1:1?
- 1.3 標記整理算法
- 優點
- 缺點
- 2. 是否移動?
- Reference
0. 分代收集理論
分代假說
現在多數JVM GC都遵循分代收集(Generational Collection)理論,其中涉及三個經驗性的分代假說:
- 弱分代假說(Weak Generational Hypothesis):絕大多說對象都是朝生夕滅的
- 對象創建先進入新生代(Young/Nursery),進行較為頻繁、局限于新生代的Minor GC
- 強分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集過程的對象,越難以消亡
- 對象經過幾輪gc后進入老年代(Old/Tenured),進行次數相對少、局限于老年代的Major GC
- 跨代引用假說(Intergenerational Reference Hypothesis):跨代引用相對于同代引用來說占比極少
- 跨代引用:比如新生代對象可能被老年代所引用,所以Minor GC時需要在固定的GC Roots外,再額外增加老年代中的Roots來保證可達性分析的正確性(young引用old同理)。
- 以新生代被老年代引用為例,根據跨代引用假說,如果遍歷整個老年代的引用關系來增加GC Roots,效率將會非常低,可以通過在新生代增加專門的數據結構(記憶集,Rememered),來標識跨代引用關系。比如,CMS和G1垃圾收集器都會通過寫前屏障的方式,將跨代引用記錄在卡表(可以看做一種記憶集)中。
分代GC定義
前面有提到Minor GC、Major GC之類的說法,其實就是不同分代中的GC行為,類似的定義包括:
- 部分收集(Partial GC): 不完整收集整個Java堆的GC。又分為:
- 新生代收集(Minor GC/Young GC):目標是新生代的GC
- 老年代收集(Major GC/Old GC):目標是老年代的GC。目前只有CMS收集器會有只針對老年代的GC。
- 混合收集(Mixed GC):目標是整個新生代和部分老年代的GC。目前只有G1收集器會有Mixed GC。
- 整堆收集(Full GC):整個Java堆和方法區的GC。
1. 垃圾回收算法
1.1 標記清除(Mark-Sweep)算法
將垃圾回收分為標記和清除兩個階段:
- 標記階段:標記出所有活躍對象(或者標記死亡對象)
- 清除階段:回收未被標記為活躍的對象(或者標記死亡的對象)
優點
實現簡單、速度快。后面的收集算法大多在此基礎上改進得來的。
缺點
- 執行效率不穩定:標記、清除的執行效率隨對象數增加而降低;
- 清除后可能造成內存碎片:如果new了一個大的對象,碎片化的內存沒法使用,造成內存浪費。
1.2 標記復制算法
將內存分為兩個區域:一個區域用于存儲存活對象,一個保留區域
- 標記處所有存活對象,移動到保留區域
- 移動到保留區域的對象進行內存整理(避免碎片化)
- 將原有區域整個清理掉,變成新的保留區域
現代的商用Java虛擬機大多采用改進的標記復制算法來進行新生代GC。
優點
效率很高,不會產生內存碎片
缺點
- 對象存活率較高時,需要很多復制操作,效率降低。比如老年代中,大部分對象存活周期都很長(前面提到過的強分代假說),所以老年代中一般不采用標記-復制算法。
- 保留區域與存活區域1:1(半區復制,Semispace Copying)的話,會有一半內存被浪費。
一些現代的垃圾收集器(ParNew等)中將新生代分為Eden區+2個Survive(Survivor)區(8:1:1,Appel式回收)解決內存浪費:new對象先分配到Eden中,將Eden與非保留區域的survivor1標記后,將存活對象移動到作為保留區域的Survivor2中,將其他區域(Eden與Survivor1)GC,survivor1成為新的保留區域。
為什么是8:1:1?
大家都知道新生代對象的壽命大部分都很短,也就是弱分代假說中的“朝生夕滅”。IBM公司有研究對對象的“朝生夕滅”做了量化,即新生代對象中98%熬不過第一輪GC。因此完全沒必要按照1:1分配新生代空間。
HotSpot虛擬機默認的Eden與Survivor比例是8:1,可以保證每次新生代可用內存空間為整個新生代的90%(Eden 80%+非保留區域survivor的10%)。
但可能存在多于10%對象存活的情況。因此,當一次Minor GC后,Survivor空間不足以容納幸存的對象,就需要老年代作為保底。
1.3 標記整理算法
- 標記階段:標記存活對象,清除垃圾對象。
- 整理階段:內存整理,讓存活對象向內存空間的一端移動。
優點
- 相比于標記-復制算法,內存使用效率高,吞吐量高;
- 同時也不會有標記-清除算法的內存碎片化問題。
這里吞吐量的定義為:運行用戶代碼時間/(運行用戶代碼時間+運行垃圾收集時間)
缺點
- 消耗時間比較長,高并發場景可能影響系統性能
大家應該多少都聽說過“Stop The World ”,也就是移動存活對象時(特別是老年代每次回收都有大量對象存活,需要大量移動操作),移動操作必須暫停用戶應用程序。因此有時候老年代空間不足或內存碎片化過于嚴重(大對象內存申請不了),導致Full GC,就會面臨STW的困境。如果想要減少STW的次數,可以適當增加新生代的比例,即大部分對象生命周期在新生代快速流轉,可以適當減少老年代的STW。
標記-清除算法也要停頓用戶線程,但是時間相對較短
2. 是否移動?
標記-清除算法跟其他兩個算法最本質的區別,在于它沒有移動操作,也因此存在內存碎片化的問題。當然,即使不移動,內存碎片化也不是沒有解決辦法,只能依賴更復雜的內存分配器和內存訪問器來解決。但是內存訪問是用戶程序最頻繁的操作之一,如果增加額外負擔,會影響吞吐量。
可見:
- 移動對象:內存回收會更加復雜,GC停頓時間較長,但吞吐量會更劃算;
- 不移動對象:內存分配時會更加復雜,GC停頓時間更短,但內存分配的額外操作會極大影響吞吐量。
因此,HotSpot虛擬機中,關注吞吐量的Parallel Scavenge收集器給予標記-整理算法,關注延遲的CMS(老年代)基于標記-清除算法(實際上CMS都會用,大多數時間標記-刪除,等碎片化道影響大對象分配時,標記-整理一次)。
最新的ZGC和Shenandoah收集器使用讀屏障是愛你整理過程和用戶線程的并發執行
Reference
《深入理解java虛擬機:JVM高級特性與最佳時間(第3版)》 周志明