如何判斷垃圾對象?

 如何判斷垃圾對象?
垃圾收集的第一步就是先需要算法來標記哪些是垃圾,然後再對垃圾進行處理。
 
引用計數(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. 緊湊(也稱“整理”):將所有存活對象向內存開始部位移動,稱爲內存緊湊(相當於碎片整理)。完畢後,清理剩餘內存空間。

 
分代收集策略
由於不同的對象適合使用不同的垃圾收集算法,所以引入“代”這個概念。不同的代有不同的分區,一般分爲新生代區和老年代區。
新生代:適合採用複製算法進行垃圾收集,對象分佈在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 分區


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

 
 
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的永久代區。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章