常用的垃圾回收算法
标記-清除
标記清除算法是一種非移動式的回收算法,分為标記 清除 2個階段,簡而言之就是先标記出需要回收的對象,标記完成後再回收掉所有标記的記憶體對象,如下圖
可見回收後圖中被标記的對象被删除回收了,但是碎片化比較嚴重不連續 對于下次配置設定大對象的時候由于記憶體不連續性影響比較大,而且每一次Gc的時候需要執行2個操作 1次标記 1次回收
标記-整理壓縮
标記整理壓縮算法是一種移動式的算法,由于上面标記清除算法導緻記憶體不連續的問題 标記-整理算法就解決了這個問題。
工作原理也是2階段操作而且更複雜了,首先找出(root)根位址的對象一直尋找标記是否被引用,引用了就标記一下,标記完成後把标記的對象按順序移動排列在一起并清除掉邊界的未标記的對象,這樣就沒有記憶體碎片。
缺點
- 由于标記完成後需要移動對象 移動的過程可能會産生STW
- 2次+調整指針
複制算法
複制算法更粗暴了,邏輯也很簡單 通常直接申明了2塊一樣大小存儲空間,每次隻使用其中1塊空間,當使用的這塊空間不夠用的時候就觸發回收操作,将存活的對象copy到另一塊空間中按順序存放,可回收的就回收删除掉,這樣一來就不會出現記憶體碎片,但是要多浪費50%的記憶體空間,主要用于年輕代 比如s0 s1亦是如此。
分代回收算法
根據對象的存活周期劃分為新生代、老年代。是以可以根據不同年代的特點使用不同的回收算法。分代收集目前是大部分JVM
- 新生代特點
- 在新生代中大量的對象産生 又有大量的對象需要銷毀,他們存活時間都比較短。基本上都是回收的時候大部分會被回收掉,隻有少量的對象是存活不回收的。
- 存活對象少,垃圾對象多這就比較适合使用複制算法,複制算法需要用到2塊記憶體空間 每次隻使用其中一塊,在jdk8中不隻是單純的劃分為s0 s1 二塊存儲空間,還新增了一塊Eden ,s0 s1的預設大小是eden的8/1 這樣設計的目的在于每次觸發回收的時候把90(eden+其中1個s區)的區域中存活的對象copy到10%的存儲中,理論上清除了90%的空間,這樣做的好處就是不需要花50%的存儲空間,隻浪費了10%的空間就實作了這個算法邏輯。
- 老年代特點
- 老年代的特點就是對象存活時間都比較長,大量的存活對象就不适合像新生代一樣用複制算法了 因為copy的成本太高,這種就比較适合标記清除算法,或者标記清除整理算法。
- 優缺點概述
- 算法名稱優點缺點标記-清除簡機關置不聯系 碎片化嚴重 效率低 2次掃描标記-壓縮整理沒有碎片效率低 2次掃描 可能會多次重置指針複制算法沒有碎片 簡單高效浪費空間
垃圾回收器
上面的垃圾算法僅僅隻是一個理論上的算法 ,正在實作這些算法的叫垃圾回收器,在工作中具體是怎麼回收工作的可以不關心,但是需要了解不同的垃圾回收器是基于哪種算法實作的,有助于出現性能問題的時候有思路去參數調優,而不是盲目的問度娘。各個年輕代 老年代垃圾回收器可組合配對方式如下圖所示
serial串行收集器
serial回收器是一個串行單線程回收器,在進行垃圾回收的時候必須暫停使用者工作線程,直到回收線程處理完成,每次回收必然會STW。比較适合跑在client端應用
ParNew收集器
ParNew回收器是新生代垃圾回收器, 就是serial的多線程版本 其它基本上serial差不多的,在ps回收器沒有出來之前parNew+cms是伺服器端首選
Parallel Scavenge收集器
常說的ps 收集器就算它,ps是一個新生代收集器采用複制算法,多線程并行收集。是jdk8的預設新生代回收器。
看起來和parNew有點一樣 反正性能就是比它要強,在應用吞吐量方面更優秀。ps一般是和Parallel Old配合使用
Serial Old收集器
Serial Old收集器是Serial的老年代版本,同樣它也是單線程收集,基于标記-整理算法,工作原理可以參考serial。
Parallel Old收集器
parallel old收集器是ps的老年代版本 是多線程收集器 基于标記-整理算法 彌補了serial old單線程的不足,工作原理參考ps收集器工作流程圖。ps+po是jdk8預設的組合 也是我在項目中實踐最多的組合。
CMS收集器
cms從jdk1.4開始引入,算是裡程碑GC産品,開啟了Java領域并發(注意并發與并行parallel的差別 并發是值回收垃圾的時候和使用者線程一起幹活,并行是指多個GC線程同時回收 )回收的方案。是一個優秀的老年代垃圾回收器。
cms從名字就能看出來是基于并發的 标記-清除算法實作的回收器,它的回收流程分為 初始标記-并發标記-重新标記-并發清除 4個階段。
-
初始标記 (initial mark)
隻是标記GC Root 根對象 會stw 但是由于隻是标記了gc roots 所有很快
-
并發标記
根據第1階段的結果繼續往下标記 這個階段是并發的 不影響使用者線程
-
重新标記
為什麼會有重新标記這個階段?是因為并發标記的時候 由于使用者線程還在運作 可能産生了新的垃圾 是以需要在标記一次,當然由于第2階段标記過一次了,這一次理論上會很快 這個階段會STW
-
并發清除
清理需要回收的對象 不影響使用者線程使用。cms有個開關(-XX:CMSFullGCsBeforeCompaction=0)預設是開啟碎片整理,由于cms清理後的空間也是有碎片存在的,是以一次清理就會整理一次碎片。此階段使用者線程同樣會産生新的垃圾 目前沒有解決清除 網上叫為浮動垃圾。
是以cms隻有在并發标記和并發清除階段是不影響使用者線程停頓的。初始标記 和 重新标記 也是劃分的區域标記的,總體上能跟控制gc停頓時間 提高使用者體驗,工作原理如下
當老年代記憶體使用到92%(-XX:CMSInitiatingOccupancyFraction=92)之後出觸發cms回收一次,如果cms在回收期間中 剩餘的記憶體不夠使用者工作線程使用了(報異常Concurrent Mode Fail) 那麼serial old回收器就成了緊急替補隊員立即進行回收一次,當然停頓的時間就更長了。由于cms部分階段是使用者線程和gc線程一起工作,如果啟動門檻值設定得太高,容易導緻使用者工作線程不夠用觸發cmf異常,性能反而降低。
G1收集器
G1垃圾回收器可以同時支援年輕代、老年代,G1并沒有在實體分區隔離,上面的提到的垃圾回收器都是實體上進行分區的,G1是由一塊一塊大小相同的region組成,雖然沒有實體上進行分區,但是依然保留了年輕代 老年代的概念。回收流程有點類似cms。也是分為初始标記、并發标記、最終标記、篩選回收 4個階段。
Region的大小可以通過G1HeapRegionSize參數進行設定,其必須是2的幂,範圍允許為1Mb到32Mb。基于堆記憶體的初始值和最大值的平均數計算分區的尺寸,平均的堆尺寸會分出約2000個Region。分區大小一旦設定,則啟動之後不會再變化。region之間采用複制算法,是以不容易産生記憶體碎片。每個Region都有一個Remembered Set。當對引用進行寫操作的時候,G1檢查該引用的對象是否在别的region中,是的話,則通過CardTable把相關引用資訊存到被引用對象的Remembered Set中。當進行記憶體回收時,把RememberSet加入到GC Roots根節點的枚舉範圍。這樣就可以保證不全堆掃描也不會有遺漏。 記憶體結構如下
- Survivor regions(年輕代-Survivor區)
- Old regions(老年代)
- Humongous regions(巨型對象區域) 占用了Region容量的50%以上對象 巨型對象比較大 一般在并發标記階段如果可以回收就直接回收了。
- Free resgions(未配置設定區域,也會叫做可用分區)-上圖中空白的區域
G1之是以這裡厲害在于它用到了一些資料結構的技巧
TLAB(Thread Local Allocation Buffer)本地線程緩沖區
PLAB(Promotion Local Allocation Buffer) 晉升本地配置設定緩沖區
Collecion Sets(CSets)待收集集合
Card Table 卡表
Remembered Sets(RSets)已記憶集合
回收流程大緻如下
- 初始标記
- 隻是标記GC Roots根對象 會stw
- 并發标記
- 從上一步标記的GC Roots開始計算可達性分析并标記 這階段耗時但是是并發的 不影響使用者線程使用
-
最終标記
上一步執行的過程中産出的變動再一次計算和标記 會stw 短暫的停頓,JVM将這段時間對象變化記錄到Remembered Set Log中,在最終标記階段把Remembered Set Log合并到Remembered Set中。
-
篩選回收
為什麼多了一步篩選再回收,在于G1在收集的時候會優先回收比較有價值的region區域,垃圾對象比較多 存活對象比較少的region就算是有價值的 這樣就能有效的提高回收效率。因為優先回收掉有價值的region而不是一下全部把堆中的全部垃圾回收完,是以回收的時間基本上能夠把控。這個階段是并行操作但是會有短暫的STW基本感覺不到。
JDK10 之前的G1中的GC隻有YoungGC,MixedGC。FullGC處理會交給單線程的Serial Old垃圾收集器。
zgc收集器
Shenandoah
參考 https://zhuanlan.zhihu.com/p/444564414
轉載請注明出處。
作者:peachyy
出處:http://www.cnblogs.com/peachyy/
出處:https://peachyy.gitee.io/
出處:https://peachyy.github.io/