分代垃圾回收機制及垃圾回收算法(轉)


分代垃圾回收

垃圾回收基礎

如下圖所示:

  • 垃圾回收器主要回收堆內存,堆內存分為:新生代和老年代
  • 對於回收新生代GC:Minor GC或者叫Young GC。回收老年代的GC叫:Major GC 或者 Old GC.
  • 需要注意Full GC:它不止回收堆內存,還會回收方法區(在JDK1.8 方法區在元空間;在JDK1.7 方法區在永久代)

分代回收的理論:

    • 把絕大多數(98%)的朝生夕死的對象放在新生代
    • 把熬過多次垃圾回收的對象就越難回收放在老年代

那么經過上述理論划分為新生代和老年代,那么垃圾回收算法是怎樣的呢?

  1. 在新生代的算法:復制算法
  2. 在老年代的算法:標記清除算法、標記整理算法

垃圾回收算法

復制算法

實現簡單、運行高效,沒有內存碎片,但是空間利用率只有一半。

復制算法的原理是什么呢?

  • 需要將內存空間一分為二,一半用起來,另一半預留
  • 如下圖所示:一半的空間用作預留的空間它在GC之前是不會分配對象的,而另一半會進行分配對象,我們加入下圖中的圓是一個對象,這時候存儲一半空間滿了,就會進行GC垃圾回收。

 

那么就有了下面的第二步。

  • 將存活的對象復制到預留的空間中去,這時候預留的空間就會進行分配對象,沒有存活的對象直接回收掉,另一半的空間格式化掉當做預留的空間。
  • 我們假設,上述的四個對象,只剩下兩個對象存活,那么這兩個對象會Copy到預留空間中,之前的空間會直接格式化,那么經過第二步變成了如下圖的結果:

  • 之后new新的對象會放在當前的空間,如果空間又滿了,就會繼續走第二步。

下面我們來看一下堆區的新生代是如何利用復制算法?

Eden區的來源:Appel式回收、提高空間利用率和空間分配擔保 Eden區的來源,主要是解決上述復制算法理論的基礎,空間利用率只有一半的問題 Eden 區占80% from區10% to區10% 幾乎所有的對象優先在Eden區分配,當Eden區滿了之后,就會判斷對象存活(98%的對象是朝生夕死的),垃圾回收,存活對象進入From區,之后Eden就會全部清空,為下一次分配對象做准備。當Eden區又滿了,就會繼續觸發垃圾回收,Eden存活的對象就會放在To區,同時From區的對象經過可達性分析存活的對象也會進入到To區。可能只看文字看的似懂非懂,下面我通過畫圖來展示。

  • Eden 區幾乎所有對象會在此分配,一般大對象是直接進入老年代

  • 當Eden區空間滿了以后,就會觸發垃圾回收,根據根可達判斷存活的對象,將存活的對象分配到From區,同時Eden區清空,為一下次分配對象做准備

  • 當Eden區空間又滿了,觸發GC,根據根可達判斷存活對象,將存活的對象復制到To區,同時From區的對象經過可達性分析存活的對象復制到To區

  1. 當Eden區又滿了,就會觸發同樣的步驟,存活的對象會在From和To之間來回復制。
  2. 經過上述n個步驟,我們知道From區和To區只有10%的空間,當Eden區滿了,而這時要將存活的對象移到To區,然而經過n個步驟存活的對象太多了超過了10%的預留空間,這時候就會觸發空間分配擔保操作, 就會進入老年代區域中。同時JVM還有一個優化的操作。(在新生代的對象沒經過一次GC就會增加分代年齡,當分代年齡達到15(不是絕對的)對象還存活,就會進入老年代區

通過上述圖示,應該將新生代的復制算法講解清楚了。新生代的復制算法利用Eden區將空間利用率降低到了只有10%是預留的,其他的空間都是利用的

注意:如果是個大對象就會直接進入老年代。

標記清除算法(Mark-Sweep)

位置不連續,產生碎片;效率略低,兩遍掃描;空間利用率100%

  • 掃描空間根據可達性分析標記可回收的對象,打上標記。

  • 掃描空間根據標記可回收的對象,進行清除。

標記清除算法會導致內存不連續,產生碎片。

假設一個對象占用的內存空間占據5個格子,但是回收后的空間,不連續的沒有可存放的位置了,分配不下去了。
也就有了標記-整理算法,對內存不連續的內存空間進行整理

標記整理算法(Mark-Compact)

  • 沒有內存碎片,效率偏低,兩遍掃描,指針需要調整。

標記整理算法的效率是偏低的,因為需要移動指針,假設一個對象的指針是在1號,而通過標記整理指針變成了10號位置,那么對應的引用就需要改變,方法要改變那么全部的線程要暫停把對應的引用進行更新。

  • 第一次掃描,標記可回收的對象,這個和標記清除算法的第一步類似

  • 第二次掃描,清除可回收的對象,整理內存空間。(這一步比標記清除算法多了一步空間整理,使空間連續)

JVM中常見的垃圾回收器

  • 單線程垃圾回收器
  • 多線程並行垃圾回收器
  • 並發垃圾回收器

回收器 回收對象和算法 回收器類型
Serial 新生代,復制算法 單線程(串行)
Parallel Scavenge 新生代,復制算法 並行的多線程回收器
ParNew 新生代,復制算法 並行的多線程收集器
Serial Old 老年代,標記整理算法 單線程(串行)
Parallel Old 老年代,標記整理算法 並行的多線程回收器
CMS 老年代,標記清除算法 並發的多線程回收器
G1 跨新生代和老年代:標記整理+化整為零 並發的多線程回收器
  • 單線程收集 Serial/Serial Old :

參數:UseSerialGC 最古老的單線程、獨占。只適用於幾十兆到一兩百兆,每次垃圾回收都會停頓 STW(Stop The World) ,停止業務線程,等垃圾回收完畢之后更新業務線程的引用。
如下圖,單線程收集
當GC的時候會暫停所有用戶線程,啟動GC線程

STW(Stop The World)垃圾回收器的發展的重點就是降低STW。隨着垃圾回收器的發展也就有了多線程的垃圾回收器和並發的垃圾回收器

  • 多線程收集 Parallel Scavenge/Parallel Old

Parallel Scavenge/Parallel Old 是JDK1.8默認的垃圾收集器
Parallel Scavenge:回收新生代
Parallel Old:回收老年代

垃圾回收器是多線程的提高回收的效率
下面是相關的參數:

 

 吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)

UseAdaptiveSizePollcy : 完成最大的吞吐量
Parallel Scavenge/Parallel Old  垃圾收集器追求的是吞吐量,但是STW並沒有減少多少。
STW會帶來哪些危害?
假設STW垃圾回收暫停了10s 而 請求響應只用了1s 那么總共請求 1s + 10s= 11s
為了減少STW的時間,於是就有了並發垃圾回收器

  • 並發垃圾回收器 CMS(Concurrent Mark Sweep)/ParNew

CMS 主要針對老年代的垃圾回收器,主要是降低STW。由於Parallel Scavenge是新生代的垃圾回收器,但是Parallel Scavenge追求的吞吐量,並不能降低STW,於是就有了ParNew 來和CMS配對,ParNew 負責新生代、CMS 負責老年代
CMS 采用的是標記清除算法,那么CMS是如何並發標記清除呢?看下面:

  • 初始標記: 標記GC roots有直接關系的對象,標記速度會非常快。這時候就會暫停業務線程

  • 並發標記: 進行GC Roots Tracing的過程,標記可回收的對象,而這時候的標記量會很大,時間會很長所以采取並發的方式,讓業務線程和並發標記一起跑,不會暫停業務線程。GC和用戶線程同時跑,肯定會有一些偏差,中間還有一些變化和沒有標記的,所以就有了重新標記
  • 重新標記:為了修正並發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分的標記記錄,這個過程會比初始標記長一些,但是遠比並發標記端,所以會暫停所有用戶線程(STW),標記剩余的,這段是時間較短的。
  • 並發清除:在上述講到的標記清除算法中,需要挨個刪除標記的可回收的對象,這種操作肯定時間比較長,所以使用並發清除讓用戶和GC同時運行,
  • 重置線程

在並發標記和並發清除可以很有效的降低STW。

CMS是一款優秀的收集器,它的主要優點在名字上已經體現出來了:並發收集、低停頓
CMS的缺點:

  • 對CPU資源敏感:面向並發設計的程序都對CPU資源比較敏感,在並發階段雖然不會導致用戶線程停頓,但是會占用一部分線程而導致應用程序變慢,總吞吐量會降低。CMS默認啟動的回收線程數時(CPU數量+3)/4,也就是當CPU在4個以上時,並發回收時垃圾收集線程不少於25%的CPU資源,當CPU不足4個時,CMS對用戶程序的影響就很可能變得很大。
  • 無法處理浮動垃圾:由於CMS並發清理階段用戶線程還在運行着,伴隨程序運行自然就還會有新的垃圾不斷產生。這一部分垃圾出現在標記過程之后,CMS無法再次收集處理他們,只好留待下一次GC時再清理掉。假如老年代100m,當到了92m觸發垃圾回收,而這時候老年代有占用了90m,而這時候產生了20m的浮動垃圾如何處理呢?就會采用Serial Old來進行替代
  • 標記清除算法導致的內存碎片,當空間碎片過多時,將會給大對象分配帶來很大麻煩,往往會出現老年代空間剩余,但無法找到足夠大連續空間來分配當前對象。

CMS 的垃圾回收器帶來的問題還是非常多的。維護起來非常麻煩的,所以在JDK1.7 JDK1.8都沒有將CMS設置成默認的垃圾回收器。但是CMS是並發標記的垃圾回收器的基石,雖然有很多問題,但是CMS的並發標記思想才有了后面G1的優秀的垃圾回收器

為什么CMS不使用標記整理算法?
  如果過采用標記整理算法,需要清理和整理的同時用戶線程還會跑,而標記整理涉及到了指針的調整,那么CMS還得在多做一步,而使用標記清除只需要標記的清除就可以了。


在CMS之后,JVM推出了G1垃圾回收器。

  • G1(Garbage First) 垃圾回收器

G1:追求停頓時間;Region區;篩選回收;可預測停頓;復制和標記整理算法

G1 將整個堆區化整為零,有了Region區:

  • E和S是新生代;
  • O 是老年代
  • H 表示大對象也是老年代

G1 垃圾回收器的運作分為如下幾個步驟:

  1. 初始標記: 僅僅只是標記一下GC Roots 能直接關聯到的對象,並且修改TAMS(Nest Top Mark Start)的值,讓下一階段用戶程序並發運行時,能在正確可以的Region中創建對象,此階段需要停頓線程,但耗時很短。
  2. 並發標記: 從GC Root 開始對堆中對象進行可達性分析,找到存活對象,此階段耗時較長,但可與用戶程序並發執行。
  3. 最終標記: 為了修正在並發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程的Remembered Set Logs里面,最終標記階段需要把Remembered Set Logs的數據合並到Remembered Set中,這階段需要停頓線程,但是可並行執行。
  4. 篩選回收 :首先對各個Region中的回收價值和成本進行排序,根據用戶所期望的GC 停頓是時間來制定回收計划。此階段其實也可以做到與用戶程序一起並發執行,但是因為只回收一部分Region,時間是用戶可控制的,而且停頓用戶線程將大幅度提高收集效率。

 開啟G1垃圾收集器的參數:

 ref:分代垃圾回收機制及垃圾回收算法 - donleo123 - 博客園 (cnblogs.com)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM