JVM中的垃圾收集算法和Heap分區簡記

如何判斷垃圾對象?

垃圾收集的第一步就是先需要算法來標記哪些是垃圾,然後再對垃圾進行處理。

 

引用計數(ReferenceCounting)算法

這種方法比較簡單直觀,FlashPlayer/Python使用該算法,簡單高效。核心思路是,給每個對象添加一個被引用計數器,被引用時+1,引用失效-1,等於0時就表示該對象沒有被引用,可以被回收。但是,Java/C#並不採用該算法,因爲該算法沒有解決對象相互引用的問題,即:當兩個對象相互引用且不被其它對象引用時,各自的引用計數爲1,雖不爲0,但仍然是可被回收的垃圾對象。

 

根搜索(GC Roots Tracing)算法

基本原理是:GCRoot對象作爲起始點(根)。如果從根到某個對象是可達的,則該對象稱爲“可達對象”(存活對象,不可回收對象)。否則就是不可達對象,可以被回收。

 

垃圾收集算法

垃圾收集器通常會假設大部分的對象的存活時間都非常短,只有少數對象的存活時間比較長。

垃圾收集算法在JVM中主要是複製算法(新生代GC)和標記/整理算法(老年代GC)。

 

標記-清除(Mark-Sweep)算法

算法過程:

1. 先判定對象是否可回收,對其標記。
2. 統一回收(簡單地刪除對垃圾對象的內存引用)。

優點:簡單直觀容易實現和理解。缺點:效率不高,內存空間碎片化。

 

複製(Copying)算法

將內存平均分成A、B兩塊,算法過程:

1. 新生對象被分配到A塊中未使用的內存當中。當A塊的內存用完了, 把A塊的存活對象對象複製到B塊。

2. 清理A塊所有對象。

3. 新生對象被分配的B塊中未使用的內存當中。當B塊的內存用完了, 把B塊的存活對象對象複製到A塊。

4. 清理B塊所有對象。

5. goto 1。

優點:簡單高效。缺點:內存代價高,有效內存爲佔用內存的一半。

 

對複製算法進一步優化:使用Eden/S0/S1三個分區

平均分成A/B塊太浪費內存,採用Eden/S0/S1三個區更合理,空間比例爲Eden:S0:S1==8:1:1,有效內存(即可分配新生對象的內存)是總內存的9/10。

算法過程:

1. Eden+S0可分配新生對象;

2. 對Eden+S0進行垃圾收集,存活對象複製到S1。清理Eden+S0。一次新生代GC結束。

3. Eden+S1可分配新生對象;

4. 對Eden+S1進行垃圾收集,存活對象複製到S0。清理Eden+S1。二次新生代GC結束。

5. goto 1。

 

標記-緊湊(Mark-Compact)

算法過程:

1. 標記:標記可回收對象(垃圾對象)和存活對象。

2. 緊湊(也稱“整理”):將所有存活對象向內存開始部位移動,稱爲內存緊湊(相當於碎片整理)。完畢後,清理剩餘內存空間。

spacer.gif

 

分代收集策略

由於不同的對象適合使用不同的垃圾收集算法,所以引入“代”這個概念。不同的代有不同的分區,一般分爲新生代區和老年代區。

新生代:適合採用複製算法進行垃圾收集,對象分佈在Eden/S0/S1三個區。

老年代:適合採用標記-緊湊算法進行垃圾收集。

 

Heap分區和分代概念

Heap分區的目的

1. 爲了分代:不同代的對象放到不同的內存分區中,實現“代提升”,也方便實現對不同分代採用不同的垃圾收集算法。

2. 垃圾收集算法需要:新生代GC使用到複製算法,該算法需要將對應的分區劃分成三個分區:Eden/S0/S1。

 

術語

Generation代

 - YongGeneration/NewGeneration:新生代,在Eden/S0/S1的存活的對象。

 - OldGeneration:老年代,在Tenured區存活的對象。
 - PermanentGeneration:永久代。
Space 區

 - Eden:伊甸園區,是新生代的一個區。
 - Survivor:倖存區,屬於新生代,爲了複製算法的需要。一般分成大小相等的兩個區(S0/S1或者From/To)。

 - Tenured:存放老年代的區域。
 - Permanent:終身區。

 

下圖:Hotspot 的 Heap 分區

spacer.gif

 

 

下圖:VisualVM 中通過 VisualGC插件顯示的分區

spacer.gif

 

 

Eden/S0/S1 新生代

[Eden                 ][S0     ][S1    ]

S0/S1是大小相當的兩個區域,共同組成Survivor區。

空間比例:Eden:S0==8:1。設定方法:-XX:SurvivorRatio=8。

新生對象在Eden/S0或者Eden/S1中分配,Eden區的對象量達到一個閾值後,發生一次新生代GC。

 

Old 老年代

每個對象有“對象年齡計數器”。對象由Eden收集到Survivor區後,年齡+1。進行新生代GC後,年齡+1。依次,當年齡>=15後進入老年代。

最大年齡閾值設定:-XX:MaxTenuringThreshold。

動態年齡:如果在Survivor中所有相同年齡對象佔用了空間的一半多,大於等於上述年齡的對象直接進入老年代。

大對象(比如大的數組)直接進入老年代。閾值設定:-XX:PretenureSizeThreshold。

 

Perm 永久代(PermanentGeneration)

用於存放不變對象,如類、方法、字符串等。

Java7把駐留字符串(intentd string)放到了老年代區。Java8中移除了Hotspot的永久代區。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章