天天看點

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

 接上一篇的java記憶體模型,這一篇記錄一下GC垃圾回收的算法,說道垃圾回收,首先說說什麼情況下會被回收。

一 垃圾回收算法

1.可達性分析算法

一般認為GC回收采用可達性分析算法::從一個被稱為GC Roots的對象開始向下搜尋,如果一個對象到GC Roots沒有任何引用鍊相連時,則說明此對象不可用。

在java中可以作為GC Roots的對象有以下幾種:

虛拟機棧中引用的對象、方法區類靜态屬性引用的對象、方法區常量池引用的對象、本地方法棧JNI引用的對象

雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象 不一定會被回收。當一個對象不可達GC Roots時,這個對象并不會馬上被回收,而是處于一個死緩的階段,若要被真正的回收需要經曆兩次标記。如果對象在可達性分析中沒有與GC Roots的引用鍊,那麼此時就會被第一次标記并且進行一次篩選,篩選的條件是是否有必要執行finalize()方法。當對象沒有覆寫finalize()方法或者已經被虛拟機調用過,那麼就認為是沒必要的。

2.标記/清除算法

 标記清除算法是垃圾處理器最初的清理算法,也是最基礎的清理算法,了解了這個後邊兩個基本就清楚了。

标記,清除算法,字面意思就是先标記,再清除

标記階段:标記所有GC roots不可達的對象,進行标記

清除階段:對上面标記過的對象進行清除回收

回收前狀态:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

回收後狀态:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結
深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

缺點:

    1.回收時需要先停止虛拟機的工作,就是stop the world.

    2.标記清除要周遊整個堆,效率低

  3.清除後會産生大量碎片區域,無法為大對象配置設定連續的記憶體

3 複制算法

複制算法是在标記清除的基礎上改進的,把記憶體區域平分為兩個部分,每次建立對象隻在一個區域内操作,

分為兩個階段

1.複制階段:當堆記憶體不足時,先停掉jvm工作,把GCRoots可達的對象(即不可回收的對象)複制到另一半記憶體空間,在新的區域按順序排列,重新配置設定對象的引用。

2.清楚階段:原來的記憶體區域都是需要回收的對象,對整個區域進行一次full gc

優點:不會産生大量的碎片空間

缺點:記憶體使用率隻有一半,确實挺浪費的,垃圾回收時同樣要 stop the world,如果老年對象多,則每次要複制大量對象。

改進:

現代的商用虛拟機一般都采用這種方法回收新生代,IBM的研究表明,新生代有98%的記憶體都是需要回收的,大部分都是朝生夕死。記憶體不必1:1配置設定,将記憶體劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,将Eden和Survivor中還存活的對象一次性地複制到另外一塊Survivor空間,最後清理掉Eden和剛才用過的Survivor空間,清理完成後,剛剛被清理的Eden和另一塊在回收時放入存活對象的Survivor空間作為使用記憶體,剛被清理的Survivor作為保留白間,以便後面用來回收之用。

這種改進的收集算法也有一個問題,就是在回收時,那塊空的Survivor空間能否放得下Eden和使用的Survivor空間中還存活的對象,如果Survivor空間不夠存放上一次新生代收集下來的存活對象,此時就需要向老年代“借”記憶體,那些剩餘未放下的對象就通過配置設定擔保機制進入老年代。 複制算法的執行過程如下: 回收前的狀态:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

回收後的狀态:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結
深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

4.标記整理算法

複制算法如果在對象存活率較高時,就需要進行較多次的複制操作,效率也會變低。而對于老年代中的對象,一般存活率都較高,是以需要選用其他收集算法:标記 - 整理算法。标記過程仍然與“标記-清除”算法中一樣,但是在标記完成後并不直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體。算法示意圖如下: 回收前狀态;

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

回收後狀态:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結
深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

5.分代收集算法

目前商業虛拟機都采用這個“分代收集”算法(Generation Collection),它根據對象存活周期的不同将記憶體劃分為幾塊,一般是把java堆分為新生代和老年代,根據各個年代的特點選用不同的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,隻有少量存活,是以可以選用“複制算法”,此時隻需要付出少量存活對象的複制成本即可;對于老年代,因為對象存活率較高、也沒有額外空間為期配置設定擔保,就必須使用“标記-清除”或“标記-整理”算法來進行回收。

二 垃圾收集器總結

這部分摘自網絡

如果說上面介紹的收集算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實作,按照上面的介紹,目前垃圾收集器基本都采用分代收集,是以 一個垃圾收集器中一般都存在多種垃圾回收算法。不同的虛拟機提供的垃圾收集器也有很大差異,如下是HotSpot虛拟機基于JDK1.7版本所包含的所有垃圾收集器:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

HotSpot中共有7中不同的垃圾收集器,如果兩個收集器之間存在連線,說明它們之間可以搭配使用,其中,Serial、ParNew、Parallel Scavenge屬于新生代收集器,CMS、Serial Old、Parallel Old屬于老年代收集器,G1是最新的一種收集器,在新生代和老年代中都可使用。

3.1 Serial(串行)收集器

最基本、發展曆史最悠久的一種收集器。看名字就知道,這個收集器是一個單線程的收集器,隻使用一個CPU或一條收集線程去完成垃圾收集工作,最重要的是,在 它進行垃圾收集的時候,必須暫停其他所有的工作線程,知道它收集結束。雖然有這個缺點,但是 依然是虛拟機運作在Client模式下的預設新生代收集器。優點是: 簡單而高效,沒有線程互動的開銷。運作過程如圖:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

新生代采用的是“複制算法”,老年代采用的是“标記-整理”算法。

3.2 ParNew收集器

ParNew收集器其實就 是Serial收集器的多線程版本,除了 使用多條線程進行垃圾收集之外,其他行為和Serial收集器一樣。ParNew是許多運作在Server模式下的虛拟機中首選的新生代收集器,其中有一個與性能無關的重要原因,除了Serial收集器外,目前隻有ParNew能與老年代的CMS收集器配合使用。 ParNew是一種并行的收集器。在垃圾回收中,并行是指:多條垃圾收集線程并行工作,使用者線程處于等待狀态;并發是指:使用者線程和垃圾收集線程同時執行(不一定并行,可能交替執行)。

3.3 Parallel Scavenge收集器

Parallel Scavenge收集器使用的是 複制算法,也是 一個并行的多線程收集器。和ParNew相似,但是Parallel Scavenge的關注點不同,CMS收集器的關注點是盡可能地縮短垃圾收集時使用者線程的停頓時間,而Parallel Scavenge收集器的目标則是達到一個可控制的吞吐量,吞吐量 = 運作使用者代碼時間 / (運作使用者代碼時間 + 垃圾收集時間)。

上面三種都是新生代收集器,下面介紹老年代收集器。

3.4 Serial Old收集器

Serial Old收集器是新生代Serial收集器的老年代版本,同樣是一個單線程收集器,使用 “标記-整理”算法,Serial Old的主要意義也是在于給Client模式下的虛拟機使用。

3.5 Parallel Old收集器

Parallel Old是新生代收集器Prarllel Scavenge的老年代版本,使用多線程和“标記-整理”算法。運作流程如下:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

3.6 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以擷取最短回收停頓時間為目标的收集器。對于網際網路站或者B/S系統的這種注重響應速度的服務端來說,CMS是很好的選擇。從名字Mark Sweep可以看出,CMS是基于“标記-清除”算法實作的,分為四個步驟: (1)初始标記(CMS initial mark):僅僅标記一GC Roots能直接關聯到的對象,這個步驟需要“stop the world”; (2)并發标記(CMS concurrent mark):就是GC Roots進行可達性分析階段,可并發執行; (3)重新标記(CMS remark):修正并發标記期間發生變動的那一部分對象,這個步驟需要“stop the world”; (4)并發清除(CMS concurrent sweep):執行清除階段。 執行過程如下:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

可以看到,初始标記和重新标記階段都是并行的,需要暫停使用者線程(過程比較短);在并發标記和并發清除階段是并發的,可以和使用者線程一起工作。

CMS的優點: 并發收集、低停頓。 CMS的缺點: (1)對CPU資源非常敏感,面向并發設計程式的通病,雖然不至于導緻使用者線程停頓,但是會降低吞吐率; (2)無法清理“浮動垃圾”,由于CMS并發清理階段使用者線程還在運作着,伴随程式運作自然就還會有新的垃圾不斷出現,這一部分垃圾出現在标記過程之後,CMS無法在當次收集中處理掉它們,隻好留待下一次的GC; (3)會産生大量空間碎片,因為CMS是基于“标記-清除”算法,這種算法的最大缺點就是會産生大量空間碎片,給配置設定大對象帶來麻煩,不得不提前觸發Full GC。為了解決這個問題,CMS提供了一個“-XX:+UseCMSCompaceAtFullCollection”的開關參數(預設開啟),用于 在CMS收集器頂不住要進行Full GC時開啟記憶體碎片的合并整理過程。

3.7 G1收集器

G1收集器是最新的一款收集器,JDK1.7才釋出,是一種面向服務端應用的垃圾收集器,有如下特點: (1)并行與并發: G1能充分利用多CPU、多核環境下的硬體優勢,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓的時間; (2)分代收集: 分代概念在G1中依然得以保留。雖然G1可以不需其他收集器配合就能獨立管理整個GC堆,但它能夠采用不同的方式去處理新建立的對象和已經存活了一段時間、熬過多次GC的舊對象以擷取更好的收集效果; (3)空間整合:與CMS的“标記-清理”算法不同,G1從整體看來是基于“标記-整理”算法實作的收集器,從局部(兩個Region之間)上看是基于“複制”算法實作,無論如何,這兩種算法都意味着G1運作期間不會産生記憶體空間碎片,收集後能提供規整的可用記憶體; (4)可預測的停頓時間;

使用G1收集器時,Java堆的記憶體布局與就與其他收集器有很大差别,它将整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是實體隔離的了,它們都是一部分Region(不需要連續)的集合。

G1的收集過程分為以下幾個步驟: (1) 初始标記(Initial Marking) (2) 并發标記(Concurrent Marking) (3)最終标記(Final Marking) (4)篩選回收(Live Data Counting and Evacuation) 前幾個步驟和CMS有很多相似之處。運作示意圖如下:

深入了解java虛拟機(二)---GC标記清除算法與垃圾回收器總結

(以上圖檔來源于: http://blog.csdn.net/ns_code/article/details/18076173

http://blog.csdn.net/zq602316498/article/details/38757423