對G1垃圾回收器的理解
版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/u012904383/article/details/79202893
1:瞭解G1
G1的第一篇paper(附錄1)發表於2004年,在2012年才在jdk1.7u4中可用。oracle官方計劃在jdk9中將G1變成預設的垃圾收集器,以替代CMS。為何oracle要極力推薦G1呢,G1有哪些優點?
首先,G1的設計原則就是簡單可行的效能調優
開發人員僅僅需要宣告以下引數即可:
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
其中-XX:+UseG1GC為開啟G1垃圾收集器,-Xmx32g 設計堆記憶體的最大記憶體為32G,-XX:MaxGCPauseMillis=200設定GC的最大暫停時間為200ms。如果我們需要調優,在記憶體大小一定的情況下,我們只需要修改最大暫停時間即可。
其次,G1將新生代,老年代的物理空間劃分取消了。
這樣我們再也不用單獨的空間對每個代進行設定了,不用擔心每個代記憶體是否足夠。
取而代之的是,G1演算法將堆劃分為若干個區域(Region),它仍然屬於分代收集器。不過,這些區域的一部分包含新生代,新生代的垃圾收集依然採用暫停所有應用執行緒的方式,將存活物件拷貝到老年代或者Survivor空間。老年代也分成很多區域,G1收集器通過將物件從一個區域複製到另外一個區域,完成了清理工作。這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms記憶體碎片問題的存在了。
在G1中,還有一種特殊的區域,叫Humongous區域。 如果一個物件佔用的空間超過了分割槽容量50%以上,G1收集器就認為這是一個巨型物件。這些巨型物件,預設直接會被分配在年老代,但是如果它是一個短期存在的巨型物件,就會對垃圾收集器造成負面影響。為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型物件。如果一個H區裝不下一個巨型物件,那麼G1會尋找連續的H分割槽來儲存。為了能找到連續的H區,有時候不得不啟動Full GC。
PS:在java 8中,持久代也移動到了普通的堆記憶體空間中,改為元空間。
物件分配策略
說起大物件的分配,我們不得不談談物件的分配策略。它分為3個階段:
TLAB(Thread Local Allocation Buffer)執行緒本地分配緩衝區
Eden區中分配
Humongous區分配
TLAB為執行緒本地分配緩衝區,它的目的為了使物件儘可能快的分配出來。如果物件在一個共享的空間中分配,我們需要採用一些同步機制來管理這些空間內的空閒空間指標。在Eden空間中,每一個執行緒都有一個固定的分割槽用於分配物件,即一個TLAB。分配物件時,執行緒之間不再需要進行任何的同步。
對TLAB空間中無法分配的物件,JVM會嘗試在Eden空間中進行分配。如果Eden空間無法容納該物件,就只能在老年代中進行分配空間。
最後,G1提供了兩種GC模式,Young GC和Mixed GC,兩種都是Stop The World(STW)的。下面我們將分別介紹一下這2種模式。
2:G1 Young GC
Young GC主要是對Eden區進行GC,它在Eden空間耗盡時會被觸發。在這種情況下,Eden空間的資料移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分資料會直接晉升到年老代空間。Survivor區的資料移動到新的Survivor區中,也有部分資料晉升到老年代空間中。最終Eden空間的資料為空,GC停止工作,應用執行緒繼續執行。
這時,我們需要考慮一個問題,如果僅僅GC 新生代物件,我們如何找到所有的根物件呢? 老年代的所有物件都是根麼?那這樣掃描下來會耗費大量的時間。於是,G1引進了RSet的概念。它的全稱是Remembered Set,作用是跟蹤指向某個heap區內的物件引用。
在CMS中,也有RSet的概念,在老年代中有一塊區域用來記錄指向新生代的引用。這是一種point-out,在進行Young GC時,掃描根時,僅僅需要掃描這一塊區域,而不需要掃描整個老年代。
但在G1中,並沒有使用point-out,這是由於一個分割槽太小,分割槽數量太多,如果是用point-out的話,會造成大量的掃描浪費,有些根本不需要GC的分割槽引用也掃描了。於是G1中使用point-in來解決。point-in的意思是哪些分割槽引用了當前分割槽中的物件。這樣,僅僅將這些物件當做根來掃描就避免了無效的掃描。由於新生代有多個,那麼我們需要在新生代之間記錄引用嗎?這是不必要的,原因在於每次GC時,所有新生代都會被掃描,所以只需要記錄老年代到新生代之間的引用即可。
需要注意的是,如果引用的物件很多,賦值器需要對每個引用做處理,賦值器開銷會很大,為了解決賦值器開銷這個問題,在G1 中又引入了另外一個概念,卡表(Card Table)。一個Card Table將一個分割槽在邏輯上劃分為固定大小的連續區域,每個區域稱之為卡。卡通常較小,介於128到512位元組之間。Card Table通常為位元組陣列,由Card的索引(即陣列下標)來標識每個分割槽的空間地址。預設情況下,每個卡都未被引用。當一個地址空間被引用時,這個地址空間對應的陣列索引的值被標記為”0″,即標記為髒被引用,此外RSet也將這個陣列下標記錄下來。一般情況下,這個RSet其實是一個Hash Table,Key是別的Region的起始地址,Value是一個集合,裡面的元素是Card Table的Index。
Young GC 階段:
階段1:根掃描
靜態和本地物件被掃描
階段2:更新RS
處理dirty card佇列更新RS
階段3:處理RS
檢測從年輕代指向年老代的物件
階段4:物件拷貝
拷貝存活的物件到survivor/old區域
階段5:處理引用佇列
軟引用,弱引用,虛引用處理
3:G1 Mix GC
Mix GC不僅進行正常的新生代垃圾收集,同時也回收部分後臺掃描執行緒標記的老年代分割槽。
它的GC步驟分2步:
全域性併發標記(global concurrent marking)
拷貝存活物件(evacuation)
在進行Mix GC之前,會先進行global concurrent marking(全域性併發標記)。 global concurrent marking的執行過程是怎樣的呢?
在G1 GC中,它主要是為Mixed GC提供標記服務的,並不是一次GC過程的一個必須環節。global concurrent marking的執行過程分為五個步驟:
初始標記(initial mark,STW)
在此階段,G1 GC 對根進行標記。該階段與常規的 (STW) 年輕代垃圾回收密切相關。
根區域掃描(root region scan)
G1 GC 在初始標記的存活區掃描對老年代的引用,並標記被引用的物件。該階段與應用程式(非 STW)同時執行,並且只有完成該階段後,才能開始下一次 STW 年輕代垃圾回收。
併發標記(Concurrent Marking)
G1 GC 在整個堆中查詢可訪問的(存活的)物件。該階段與應用程式同時執行,可以被 STW 年輕代垃圾回收中斷
最終標記(Remark,STW)
該階段是 STW 回收,幫助完成標記週期。G1 GC 清空 SATB 緩衝區,跟蹤未被訪問的存活物件,並執行引用處理。
清除垃圾(Cleanup,STW)
在這個最後階段,G1 GC 執行統計和 RSet 淨化的 STW 操作。在統計期間,G1 GC 會識別完全空閒的區域和可供進行混合垃圾回收的區域。清理階段在將空白區域重置並返回到空閒列表時為部分併發。
三色標記演算法
提到併發標記,我們不得不瞭解併發標記的三色標記演算法。它是描述追蹤式回收器的一種有用的方法,利用它可以推演回收器的正確性。 首先,我們將物件分成三種類型的。
黑色:根物件,或者該物件與它的子物件都被掃描
灰色:物件本身被掃描,但還沒掃描完該物件中的子物件
白色:未被掃描物件,掃描完成所有物件之後,最終為白色的為不可達物件,即垃圾物件
當GC開始掃描物件時,按照如下圖步驟進行物件的掃描:
根物件被置為黑色,子物件被置為灰色。
繼續由灰色遍歷,將已掃描了子物件的物件置為黑色。
遍歷了所有可達的物件後,所有可達的物件都變成了黑色。不可達的物件即為白色,需要被清理。
這看起來很美好,但是如果在標記過程中,應用程式也在執行,那麼物件的指標就有可能改變。這樣的話,我們就會遇到一個問題:物件丟失問題
我們看下面一種情況,當垃圾收集器掃描到下面情況時:
這時候應用程式執行了以下操作:
A.c=C
B.c=null
這樣,物件的狀態圖變成如下情形:
這時候垃圾收集器再標記掃描的時候就會下圖成這樣:
很顯然,此時C是白色,被認為是垃圾需要清理掉,顯然這是不合理的。那麼我們如何保證應用程式在執行的時候,GC標記的物件不丟失呢?有如下2中可行的方式:
在插入的時候記錄物件
在刪除的時候記錄物件
剛好這對應CMS和G1的2種不同實現方式:
在CMS採用的是增量更新(Incremental update),只要在寫屏障(write barrier)裡發現要有一個白物件的引用被賦值到一個黑物件 的欄位裡,那就把這個白物件變成灰色的。即插入的時候記錄下來。
在G1中,使用的是STAB(snapshot-at-the-beginning)的方式,刪除的時候記錄所有的物件,它有3個步驟:
1,在開始標記的時候生成一個快照圖示記存活物件
2,在併發標記的時候所有被改變的物件入隊(在write barrier裡把所有舊的引用所指向的物件都變成非白的)
3,可能存在遊離的垃圾,將在下次被收集
這樣,G1到現在可以知道哪些老的分割槽可回收垃圾最多。 當全域性併發標記完成後,在某個時刻,就開始了Mix GC。這些垃圾回收被稱作“混合式”是因為他們不僅僅進行正常的新生代垃圾收集,同時也回收部分後臺掃描執行緒標記的分割槽。混合式垃圾收集如下圖:
混合式GC也是採用的複製的清理策略,當GC完成後,會重新釋放空間。
來自:http://blog.jobbole.com/109170/
更多G1介紹:http://blog.csdn.net/zhanggang807/article/details/45956325
---------------------
作者:我是周洲
來源:CSDN
原文:https://blog.csdn.net/zhou2s_101216/article/details/79202893
版權宣告:本文為博主原創文章,轉載請附上博文連結!