天天看點

JVM的垃圾回收政策

1.标記-清除算法:

算法分兩個階段:标記和清除階段,首先會标記出所有需要回收的對象,在标記完後統一回收所有被标記的對象,它是最基礎的垃圾回收算法。因為後續的垃圾收集算法主要基于這種思路對其不足進行改進。它的不足有兩個:一個是效率問題,标記和清除兩個過程效率都不高,另一個是空間問題,标記清除後産生大量的不連續的空間碎片,太多的碎片導緻以後在程式運作過程配置設定較大的對象時候因為找不到足夠大的記憶體而不得不提前觸發另一次的垃圾回收動作。

2.複制算法:

為了解決标記清除效率問題,複制算法就出現,它把記憶體容量分成兩等份,每次隻使用其中一塊,當這一塊記憶體用完之後,就将存活的對象複制到另一塊記憶體上去,然後把已使用的記憶體空間一次清理掉,這樣使得每次都對整個半區進行回收,記憶體分派也不用再考慮碎片的情況,隻需堆頂指針移動,按順序配置設定即可。但算法的代價是把記憶體縮成一半,成本太高。堆記憶體中的新生代一般采用這方法回收,将記憶體分為Eden和兩塊較小的Survior空間,比例為8:1,每次隻使用Eden和Survior0,當回收時候,将Eden和Survior0的存活對象複制到Survior1上,最後清理Eden和Survior0的空間。當Survior空間不夠用時,需要依賴其他記憶體(老年代)進行配置設定擔保。

3.标記整理算法

複制算法在對象存活率比較高時候就要進行多複制的操作,效率會比較低,更關鍵是,如果另一塊記憶體不夠,就要使用配置設定擔保機制,以應對所有對象100%存活的極端情況,是以老年代不能直接用這種算法。

根據老年代的特點,有人提出标記整理算法,标記過程跟标記清除一樣,但後續步驟不是直接對對象的回收,而是讓存活對象都向一端移動,然後清理掉端邊的以外的記憶體。

4.分代收集算法

目前虛拟機都采用分代收集算法,這種算法并沒什麼的新的思想,隻是根據對象存活周期不同将記憶體劃分為幾塊,一般是把JAVA堆分為新生代和老年代,這樣根據各個年代的特點采用适當的算法,在新生代中發現有大批對象死去,少量存活,則采用複制算法,隻需要複制少量存活對象就可以完成收集,而老年代因為對象存活率較高,沒有額外的空間再對它進行配置設定擔保,就必須采用标記-清除或者标記-整理算法來回收。

那麼如何判斷一個對象是否能回收呢?

引用計數算法:

給一個對象中添加一個引用計數器,每當有一個地方引用它時候計數器+1,當引用失效時候計數器減一,當計數器為0時候就是不能在使用,也就是可以回收的對象,但如果存在互相引用的話,這種方法就容易失效。

可達性算法:

主流的虛拟機都是通過可達性算法判斷對象是否存活的,通過一些列GC ROOTS的對象為起點,從這些起點向下搜尋,搜尋過的路為引用連,當一個對象到GC ROOTS沒有任何引用鍊時候,則表示這個對象是不可用的

在JAVA中作為GCROOTS對象有:

a.虛拟機棧中引用的對象

b.方法區中靜态屬性引用的對象。

c.方法區中常量引用的對象

d.Native方法中JNI引用的對象。