常見GC算法,CMS以及G1的垃圾回收過程,CMS的各個階段哪兩個是Stop the world的,CMS會不會產生碎片,G1的優勢。


常見GC算法

在C/C++中是由程序員自己去申請、管理和釋放內存的,因此沒有GC的概念。而在Java中,專門有一個用於垃圾回收的后台線程來進行監控、掃描,自動將一些無用的內存進行釋放。下面介紹幾種常見的GC算法。

引用計數法 Reference Counting

給對象添加一個引用計數器,每過一個引用計數器值就+1,少一個引用就-1。當它的引用變為0時,該對象就不能再被使用。它的實現簡單,但是不能解決互相循環引用的問題。

根搜索算法 GC Roots Tracing

以一系列叫“GC Roots”的對象為起點開始向下搜索,走過的路徑稱為引用鏈(Reference Chain),當一個對象沒有和任何引用鏈相連時,證明此對象是不可用的,用圖論的說法是不可達的。那么它就會被判定為是可回收的對象。

JAVA里可作為GC Roots的對象 
虛擬機棧(棧幀中的本地變量表)中引用的對象 
方法區中的類靜態屬性引用的對象 
方法區中的常量引用的對象 
本地方法棧中JNI(即Native方法)的引用的對象

標記-清除算法 Mark-Sweep

這是一個非常基本的GC算法,它是現代GC算法的思想基礎,分為標記和清除兩個階段:先把所有活動的對象標記出來,然后把沒有被標記的對象統一清除掉。但是它有兩個問題,一是效率問題,兩個過程的效率都不高。二是空間問題,清除之后會產生大量不連續的內存。

 

復制算法 Copying

復制算法是將原有的內存空間分成兩塊,每次只使用其中的一塊。在GC時,將正在使用的內存塊中的存活對象復制到未使用的那一塊中,然后清除正在使用的內存塊中的所有對象,並交換兩塊內存的角色,完成一次垃圾回收。它比標記-清除算法要高效,但不適用於存活對象較多的內存,因為復制的時候會有較多的時間消耗。它的致命缺點是會有一半的內存浪費。

 

標記整理算法 Mark-Compact

標記整理算法適用於存活對象較多的場合,它的標記階段和標記-清除算法中的一樣。整理階段是將所有存活的對象壓縮到內存的一端,之后清理邊界外所有的空間。它的效率也不高。

 

CMS以及G1的垃圾回收過程

我們先回顧一下主流Java的垃圾回收器(HotSpot JVM)。本文是針對堆的垃圾回收展開討論的。

堆被分解為較小的三個部分。具體分為:新生代、老年代、持久代。

image
  1. 絕大部分新生成的對象都放在Eden區,當Eden區將滿,JVM會因申請不到內存,而觸發Young GC ,進行Eden區+有對象的Survivor區(設為S0區)垃圾回收,把存活的對象用復制算法拷貝到一個空的Survivor(S1)中,此時Eden區被清空,另外一個Survivor S0也為空。下次觸發Young GC回收Eden+S0,將存活對象拷貝到S1中。新生代垃圾回收簡單、粗暴、高效。
  2. 若發現Survivor區滿了,則將這些對象拷貝到old區或者Survivor沒滿但某些對象足夠Old,也拷貝到Old區(每次Young GC都會使Survivor區存活對象值+1,直到閾值)。 3.Old區也會進行垃圾收集(Young GC),發生一次 Major GC 至少伴隨一次Young GC,一般比Young GC慢十倍以上。
  3. JVM在Old區申請不到內存,會進行Full GC。Old區使用一般采用Concurrent-Mark–Sweep策略回收內存。

總結:Java垃圾回收器是一種“自適應的、分代的、停止—復制、標記-清掃”式的垃圾回收器。

缺點:

  1. GC過程中會出現STW(Stop-The-World),若Old區對象太多,STW耗費大量時間。
  2. CMS收集器對CPU資源很敏感。
  3. CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。
  4. CMS導致內存碎片問題。

G1收集器

在G1中,堆被划分成 許多個連續的區域(region)。每個區域大小相等,在1M~32M之間。JVM最多支持2000個區域,可推算G1能支持的最大內存為2000*32M=62.5G。區域(region)的大小在JVM初始化的時候決定,也可以用-XX:G1HeapReginSize設置。

在G1中沒有物理上的Yong(Eden/Survivor)/Old Generation,它們是邏輯的,使用一些非連續的區域(Region)組成的。

新生代收集

G1的新生代收集跟ParNew類似,當新生代占用達到一定比例的時候,開始出發收集。

image
image

被圈起的綠色部分為新生代的區域(region),經過Young GC后存活的對象被復制到一個或者多個區域空閑中,這些被填充的區域將是新的新生代;當新生代對象的年齡(逃逸過一次Young GC年齡增加1)已經達到某個閾值(ParNew默認15),被復制到老年代的區域中。

回收過程是停頓的(STW,Stop-The-Word);回收完成之后根據Young GC的統計信息調整Eden和Survivor的大小,有助於合理利用內存,提高回收效率。

回收的過程多個回收線程並發收集。

老年代收集

和CMS類似,G1收集器收集老年代對象會有短暫停頓。

  1. 標記階段,首先初始標記(Initial-Mark),這個階段是停頓的(Stop the World Event),並且會觸發一次普通Mintor GC。對應GC log:GC pause (young) (inital-mark)
  2. Root Region Scanning,程序運行過程中會回收survivor區(存活到老年代),這一過程必須在young GC之前完成。
  3. Concurrent Marking,在整個堆中進行並發標記(和應用程序並發執行),此過程可能被young GC中斷。在並發標記階段,若發現區域對象中的所有對象都是垃圾,那個這個區域會被立即回收(圖中打X)。同時,並發標記過程中,會計算每個區域的對象活性(區域中存活對象的比例)。
    image
  4. Remark, 再標記,會有短暫停頓(STW)。再標記階段是用來收集 並發標記階段 產生新的垃圾(並發階段和應用程序一同運行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
  5. Copy/Clean up,多線程清除失活對象,會有STW。G1將回收區域的存活對象拷貝到新區域,清除Remember Sets,並發清空回收區域並把它返回到空閑區域鏈表中。
    image
  6. 復制/清除過程后。回收區域的活性對象已經被集中回收到深藍色和深綠色區域。
image

關於Remembered Set概念:G1收集器中,Region之間的對象引用以及其他收集器中的新生代和老年代之間的對象引用是使用Remembered Set來避免掃描全堆。G1中每個Region都有一個與之對應的Remembered Set,虛擬機發現程序對Reference類型數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的對象是否處於不同的Region之間(在分代中例子中就是檢查是否老年代中的對象引用了新生代的對象),如果是便通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set中。當內存回收時,在GC根節點的枚舉范圍加入Remembered Set即可保證不對全局堆掃描也不會有遺漏。

G1雖然保留了CMS關於代的概念,但是代已經不是物理上連續區域,而是一個邏輯的概念。在標記過程中,每個區域的對象活性都被計算,在回收時候,就可以根據用戶設置的停頓時間,選擇活性較低的區域收集,這樣既能保證垃圾回收,又能保證停頓時間,而且也不會降低太多的吞吐量。Remark階段新算法的運用,以及收集過程中的壓縮,都彌補了CMS不足。引用Oracle官網的一句話:“G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS)”。

 


免責聲明!

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



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