jvm垃圾回收機制 二
上一節主要介紹了新生代的 Serial / PraNew / Parallel Scavenge 三種垃圾回收方法和老年代的serial old 和 paralle old收集器,本節主要介紹CMS和G1垃圾收集器。
目錄
CMS收集器-標記整理演算法
CMS(Concurrent Mark Sweep)收集器的設計目標是:獲取最短回收停頓時間的收集器。
HotSpot在JDK1.5
推出的第一款真正意義上的併發(Concurrent)收集器; 第一次實現了讓垃圾收集執行緒與使用者執行緒(基本上)同時工作。目前很大一部分的Java應用集中在B/S系統的服務端上,這類應用尤其重視伺服器的響應速度,希望系統停頓時間最短,以給使用者帶來較好的體驗。而這也恰恰是CMS所擅長的地方。
收集過程
CMS的垃圾收集一共需要經過四個階段:
- 初始標記:此階段會產生STW;這個過程只是標記出GC ROOTS能直接關聯到的物件,耗時短,速度很快;
- 併發標記: 這個步是根據前面提到的GC ROOTS 根搜尋演算法,會判定物件是“存活”還是“已死”;比如說 A -> B (A 引用 B,假設 A 是 GC Roots 關聯到的物件),那麼這個階段就是標記出 B 物件, A 物件會在初始標記中標記出來。
- 重新標記: 此階段會產生STW,主要是為了檢查校驗併發標記期間,因使用者程式繼續執行而產生變動的那一部分物件的標記記錄和新物件;
- 併發清除:該步就是清除系統中無用“以死”的物件;之後將為下次gc做準備;
優點 | 併發收集,此階段比較耗時;由於採用併發收集可以減少停頓時間; |
缺點 |
(1)由於和使用者執行緒一起工作,CMS收集器對CPU資源非常敏感。CPU個數少於4個時,CMS對於使用者程式的影響就可能變得很大,出現cpu資源的競爭; (2)CMS收集器無法處理浮動垃圾,可能出現“promotion fail -> Concurrent Mode Failure”失敗而導致另一次Full GC的產生。在JDK1.5的預設設定下,CMS收集器當老年代使用了68%的空間後就會被啟用; (3)CMS是基於“標記-清除”演算法實現的收集器,手機結束時會有大量空間碎片產生。空間碎片過多,可能會出現老年代還有很大空間剩餘,但是無法找到足夠大的連續空間來分配當前物件,不得不提前出發FullGC; (4) 產生2次STW; |
使用場景 | 重視伺服器的響應速度,希望系統停頓時間最短,以給使用者帶來較好的體驗。犧牲吞吐量來獲取較小的暫停時間。 |
引數使用 |
CMS 的 GC 日誌 就是 CMS。 |
增量式併發收集器
為了解決缺點(1)cpu敏感的問題,虛擬機器提供了一種稱為“增量式併發收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器變種,所做的事情和單CPU年代PC機作業系統使用搶佔式來模擬多工機制的思想一樣,就是在併發標記、清理的時候讓GC執行緒、使用者執行緒交替執行,儘量減少GC執行緒的獨佔資源的時間,這樣整個垃圾收集的過程會更長,但對使用者程式的影響就會顯得少一些,也就是速度下降沒有那麼明顯。實踐證明,增量時的CMS收集器效果很一般,在目前版本中,i-CMS已經被宣告為“deprecated”,即不再提倡使用者使用。
CMS 產生的問題
CMS並行GC是大多數應用的最佳選擇,然而, CMS並不是完美的,在使用CMS的過程中會產生2個最讓人頭痛的問題:
- promotion failed
- concurrent mode failure
問題解析: 第一個問題promotion failed是在進行Minor GC時,Survivor Space放不下,物件只能放入老年代,而此時老年代也放不下造成的,多數是由於老年帶有足夠的空閒空間,但是由於碎片較多,這時如果新生代要轉移到老年帶的物件比較大,所以,必須儘可能提早觸發老年代的CMS回收來避免這個問題(promotion failed時老年代CMS還沒有機會進行回收,又放不下轉移到老年帶的物件,CMS執行期間預留的記憶體無法滿足程式其他執行緒需要,因此會出現下一個問題concurrent mode failure,從而回退到:stop-the-wold GC- Serail Old)。
儘管CMS使用一個叫做分配擔保的機制,每次Minor GC之後要保證新生代的空間survivor + eden > 老年帶的空閒空間,但是物件分配是不可預測的,總會有寫物件分配在老年帶是滿足不了的。
這個問題的直接影響就是它會導致提前進行CMS Full GC, 儘管這個時候CMS的老年帶並沒有填滿,只不過有過多的碎片而已,但是Full GC導致的stop-the-wold是難以接受的。
導致以上兩個問題的主要原因是:空間不夠,或者是因為空間總量夠,但是由於碎片導致了沒有連續的大的儲存空間。
對於問題“promotion failed” 解決辦法:
- -XX:UseCMSCompactAtFullCollection 每次CMS完進行碎片的整理;-XX:CMSFullGCBeforeCompaction=5 5次CMS後進行一次
- -Xmn XX:SurvivorRatio 預設為8,通過它調大新生代或者救助空間 ;
對於問題“concurrent mode failure” 解決辦法:
- +XX:CMSInitiatingOccupancyFraction 提前進行CMS垃圾回收,預設的值是68%,可以適當的調小;
- -Xms -Xmx調大老年帶的空間;
G1(Garbage First)收集器
這是一款兼顧新生代和老年代垃圾收集器,是JDK1.7提供的一個新的面向服務端應用的垃圾收集器,用於取代CMS垃圾回收。
收集過程
G1收集器的執行步驟主要分為以下4步:
1. 初始標記(Initial Marking)
該階段會STW。掃描根集合,僅標記一下GC Roots能直接關聯到的物件,將所有通過根集合直達的物件壓入掃描棧,等待後續的處理。在G1中初始標記階段是藉助Young GC的暫停進行的,不需要額外的暫停。雖然加長了Young GC的暫停時間,但是從總體上來說還是提高的GC的效率。
2. 併發標記(Concurrent Marking)
該階段不需要STW。這個階段不斷的從掃描棧中取出物件進行掃描,將掃描到的物件的欄位再壓入掃描棧中,依次遞迴,直到掃描棧為空,也就是說trace了所有GCRoot直達的物件。同時這個階段還會掃描SATB write barrier所記錄下的引用。此步比較耗時,但是和應用程式一起工作,併發執行,和CMS一樣,效率提高。
3. 最終標記(Final Marking)
這個階段會STW,最終標記主要為了找出程式在併發標記期間因使用者程式繼續運作而發生變化的物件。這個階段會處理在併發標記階段write barrier記錄下的引用,同時進行弱引用的處理。這個階段與CMS的最大的區別是CMS在這個階段會掃描整個根集合,Eden也會作為根集合的一部分被掃描,因此耗時可能會很長。
4. 篩選回收(Live Data Counting and Evacuation)
該階段會STW。清點和重置標記狀態。這個階段有點像mark-sweep中的sweep階段,這個階段並不會實際上去做垃圾的收集,只是去根據停頓模型在CSet選出任意多個Region作為垃圾收集的目標,等待evacuation階段來回收。篩選就是依據使用者設定【-XX:MaxGCPauseMillis 】的允許的GC時長,在Cset裡對排序的各個Region的回收價值和成本預估,控制GC停頓時間來制定回收計劃,達到使用者的期望;
G1特點
簡單調優
G1的設計原則就是簡單可行的效能調優,開發人員僅僅需要宣告以下引數即可:
-XX:+UseG1GC -Xmx4g -XX:MaxGCPauseMillis=200
- -XX:+UseG1GC //為開啟G1垃圾收集器,
- -Xmx4g //設計堆記憶體的最大記憶體為4G,
- -XX:MaxGCPauseMillis=200 //設定GC的最大暫停時間為200ms。
如果我們需要調優,在記憶體大小一定的情況下,我們只需要修改最大暫停時間即可,簡單方便。G1將新生代,老年代的物理空間劃分取消了。
區域(Region)
G1裡面的Region的概念不同於傳統的垃圾回收演算法中的分割槽的概念,但是仍然保留裡分代的思想。G1預設把堆記憶體分為1024個分割槽,後續垃圾收集的單位都是以Region為單位的,仍然屬於分代收集器。Region是實現G1演算法的基礎,每個Region的大小相等,通過-XX:G1HeapRegionSize引數可以設定Region的大小,這樣我們再也不用單獨的空間對每個代進行設定了,不用擔心每個代記憶體是否足夠。
從圖8中可以看出各個區域邏輯上並不是連續的。並且一個Region在某一個時刻是Eden,在另一個時刻就可能屬於老年代。G1在進行垃圾清理的時候就是將一個Region的物件拷貝到另外一個Region中。
Humongous區域
在G1中,還有一種特殊的區域,叫Humongous區域。 如果一個物件佔用的空間超過了分割槽容量50%以上,G1收集器就認為這是一個巨型物件。這些巨型物件,預設直接會被分配在年老代,但是如果它是一個短期存在的巨型物件,就會對垃圾收集器造成負面影響。為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型物件。如果一個H區裝不下一個巨型物件,那麼G1會尋找連續的H分割槽來儲存。為了能找到連續的H區,有時候不得不啟動Full GC。
物件的分配策略
物件的分配策略分為3個階段:
- TLAB(Thread Local Allocation Buffer)執行緒本地分配緩衝區;
- Eden區中分配;
- Humongous區分配;
TLAB為執行緒本地分配緩衝區,它的目的為了使物件儘可能快的分配出來。如果物件在一個共享的空間中分配,我們需要採用一些同步機制來管理這些空間內的空閒空間指標。在Eden空間中,每一個執行緒都有一個固定的分割槽用於分配物件,即一個TLAB。分配物件時,執行緒之間不再需要進行任何的同步。對TLAB空間中無法分配的物件 -> JVM會嘗試在Eden空間中進行分配 -> 如果Eden空間無法容納該物件,就只能在老年代中進行分配空間。
G1的垃圾收集模式
- YoungGC:收集年輕代裡的Region;
- MixGC:年輕代的所有Region+全域性併發標記階段選出的收益高的Region;
Young GC
工作方式:Young GC主要是對Eden區進行GC,它在Eden空間耗盡時會被觸發。在這種情況下,Eden空間的資料移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分資料會直接晉升到年老代空間。Survivor區的資料移動到新的Survivor區中,也有部分資料晉升到老年代空間中。最終Eden空間的資料為空,GC停止工作,應用執行緒繼續執行。
Mixed GC
如名字一樣,“混合收集” 不僅進行正常的新生代垃圾收集,同時也回收執行緒標記的老年代分割槽。它的GC步驟主要分2步:
(1)全域性併發標記(global concurrent marking),在G1 gc之前,會進行全域性標記;
(2)拷貝存活物件(evacuation),排序預估回收;
具體執行步驟可參考圖1。下面我們來看一下在G1工作的過程中的幾個重要的問題。
G1問題
1、如果僅僅是GC回收新生代物件,如何解決不同Region區域的引用,如何找到所有的根物件呢?
在垃圾回收的時候都是從Root開始搜尋,這會先經過年輕代再到老年代,對於年輕代引用老年代的這種跨代不需要單獨處理。但是老年代引用年輕代的會影響young gc,這種跨代需要處理。這裡CMS和G1都用到了Card Table,一個Card Table將一個分割槽在邏輯上劃分為固定大小的連續區域,每個區域稱之為卡,卡通常較小,一個位元組對應一個Card。當一個Card上的物件的引用發生變化的時候,就將這個Card對應的Card Table上的狀態置為dirty,young gc的時候掃描狀態是dirty的Card即可,CMS的老年代就會記錄這樣一個Card tbale。對於G1垃圾收集,又引入了Rset(Remembered Set),在老年代中有一塊區域用來記錄指向新生代的引用。 無論G1還是其他分代收集器,JVM都是使用Card table來避免全域性掃描。
我們知道G1垃圾收集器將記憶體分為了不同的Region區域,不再以嚴格的年輕代和老年代來區分記憶體並進行垃圾回收,如果需要掃描整個old區,勢必會浪費很多的時間,且掃描了一些不必要的Region區域。 G1通過RSet,每個Region中都有一個RSet,記錄的是其他Region中的物件引用本Region物件的關係,是一種point-in的關係,即:誰引用了我的物件。因為G1分了很多Region,需要回收那個區域的時候,只需要判斷要回收的區域是否有其他物件引用了該區域裡的物件,即只需要找待回收區域的根物件即可,避免無效掃描。若儲存point-out關係,將會掃描很多無關的Region區,造成時間效能的浪費。這裡面還有另外一個集合:Collection Set,簡稱:CSet,CSet記錄的是GC要收集的Region的集合,CSet裡的Region可以是任意代的。在GC的時候,對於old->young和old->old的跨代物件引用,只要掃描對應的CSet中的RSet即可。如果Rset集合的引用物件較多,這裡為了提高引用物件的查詢和賦值處理問題,又通過卡表(Card Table)來實現查詢和賦值,一個Card Table將一個分割槽在邏輯上劃分為固定大小的連續區域,每個區域稱之為卡。卡通常較小,介於128到512位元組之間。RSet其實是一個Hash Table,Key是別的呼叫方Region的起始地址,Value是一個集合,裡面的元素是Card Table的Index分割槽的地址。如下圖9所示:
如上圖所示,要回收年輕代的region A,只需要掃描C,D,F 區域的根物件即可,而不需要掃描整個old區。
分代G1模式下選擇CSet有兩種子模式,分別對應YoungGC和mixedGC:
- YoungGC:CSet就是所有年輕代裡面的Region;
- MixedGC:CSet是所有年輕代裡的Region加上在全域性併發標記階段標記出來的收益高的Region;
2、如何解決物件在GC過程中分配的問題呢?
初始快照演算法:snapshot-at-the-beginning (SATB),SATB是維持併發GC的一種手段。G1併發的基礎就是SATB。SATB可以理解成在GC開始之前對堆記憶體裡的物件做一次快照,此時活的物件就認為是活的,從而形成一個物件圖。在GC收集的時候,新產的物件認為是活的物件,除此之外其他不可達的物件都認為是垃圾物件。
如何找到在GC的過程中分配的物件呢?每個region記錄著兩個top-at-mark-start(TAMS)指標,分別為prevTAMS和nextTAMS。在TAMS以上的物件就是新分配的,因而被視為隱式marked。通過這種方式我們就找到了在GC過程中新分配的物件,並把這些物件認為是活的物件。
3、在GC過程中引用發生變化的問題怎麼解決呢?
三色標記演算法
在併發標記中,通過三色標記法來完成對物件是否存活以及追蹤的記錄。比如我們定義三種顏色並賦予以下的意義:
- 黑色:根物件,或者該物件與它的子物件都被掃描
- 灰色:物件本身被掃描,但還沒掃描完該物件中的子物件
- 白色:未被掃描物件,掃描完成所有物件之後,最終為白色的為不可達物件,即垃圾物件;
如果在GC執行中,物件的引用關係發生來如下的變化
如圖11所示,gc執行過程中,C物件的引用關係發生來改變,D引用C顯然按照三色標記法C為白色是要被清理的,顯然不太合理。所以這裡需要記錄此種改變。
在CMS採用的是增量更新(Incremental update),只要在寫屏障(write barrier)裡發現要有一個白物件的引用被賦值到一個黑物件 的欄位裡,那就把這個白物件變成灰色的,即插入的時候記錄下來。
write_barrier(obj,field,newobj){
if(newobj.mark == FALSE){
newobj.mark = TRUE
push(newobj,$mark_stack)
}
*field = newobj
}
在G1中,通過Write Barrier就可以瞭解到哪些引用物件發生了什麼樣的變化,刪除的時候記錄所有的物件,它有3個步驟:
(1) 在開始標記的時候生成一個快照圖SATB標記存活物件;
(2) 在併發標記的時候所有被改變的物件入隊(在write barrier裡把所有舊的引用所指向的物件都變成非白的);
(3) 可能存在遊離的垃圾,將在下次被收集;
4、可預測的停頓
G1記錄跟蹤了各個Region獲取垃圾收集的價值大小,在後臺維護一個優先列表;每次根據使用者設定的允許的收集時間,優先回收價值最大的Region,可以有計劃的避免全區的垃圾收集,這也是Garbage-First的由來,也是與CMS最大的區別;這就保證了在有限的時間內可以獲取儘可能高的收集效率;
5、什麼情況下會發生fullgc?
導致CMS FullGC的原因有兩個:
1. Promotion Failure
在年輕代晉升的時候老年代沒有足夠的連續空間容納,很有可能是記憶體碎片導致的。
2. Concurrent Mode Failure
在併發過程中jvm覺得在併發過程結束前堆就會滿了,需要提前觸發Full GC。
導致G1 Full GC的原因可能有兩個,與CMS類似:
1. Evacuation的時候沒有足夠的to-space來存放晉升的物件;
2. 併發處理過程完成之前空間耗盡
G1的初衷就是要避免Full GC的出現,Full GC會會對所有region做Evacuation-Compact,而且是單執行緒的STW,非常耗時間。
其他:
system.gc()呼叫;
永久代空間不足,注意動態代理,反射,常量等用的比較多的服務;
6、Full-GC的影響?
- 服務體驗:服務停止,嚴重影響使用者的體驗;
- 資料一致性:分散式服務中容易引起主備切換,出現腦裂;
注意點
(1)不斷調優暫停時間指標
通過XX:MaxGCPauseMillis=x可以設定啟動應用程式暫停的時間,G1在執行的時候會根據這個引數選擇CSet來滿足響應時間的設定。一般情況下這個值設定到100ms或者200ms都是可以的(不同情況下會不一樣),但如果設定成50ms就不太合理。暫停時間設定的太短,就會導致出現G1跟不上垃圾產生的速度,最終退化成Full GC。所以對這個引數的調優是一個持續的過程,逐步調整到最佳狀態。
(2)不要設定新生代和老年代的大小
G1收集器在執行的時候會調整新生代和老年代的大小。通過改變代的大小來調整物件晉升的速度以及晉升年齡,從而達到我們為收集器設定的暫停時間目標。設定了新生代大小相當於放棄了G1為我們做的自動調優。我們需要做的只是設定整個堆記憶體的大小,剩下的交給G1自己去分配各個代的大小。
G1的執行過程是這樣的,會在Young GC和Mix GC之間不斷的切換執行,同時定期的做全域性併發標記,在實在趕不上回收速度的情況下使用Full GC(Serial GC)。初始標記是搭在YoungGC上執行的,在進行全域性併發標記的時候不會做Mix GC,在做Mix GC的時候也不會啟動初始標記階段。當MixGC趕不上物件產生的速度的時候就退化成Full GC,這一點是需要重點調優的地方。
優點 |
G1主要用來取代CMS垃圾收集器,優點是: 1、簡單:開發者控制調優變的簡單; 2、並行與併發:充分利用cpu,減少STW的時間; 3、可預見性:可預測的停頓模型,G1可選取部分割槽域進行回收,可以縮小回收範圍,控制減少全域性停頓; 5、劃分模式:堆記憶體的劃分Region; 6、大空間分配:超大堆的表現更出色; |
缺點 | 會產生3次STW,但是時間較短; |
使用場景 | Java 9 的預設垃圾收集器,該收集器和之前的收集器大不相同,該收集器可以工作在young 區,也可以工作在 old 區。 |
引數使用 |
|
CMS與G1的區別:
(1) 分代收集 重新標記過程 和 回收方式(可預測回收模型)的不同。 |
總結
引數的設定及垃圾回收器的選擇一定要根據具體的服務及場景來判斷選擇,沒有完美的唯一解決方案。比如C端伺服器互動密集型,需要保證吞吐量,我們可以選擇G1 或者 “吞吐量優先”的 Parallel Scavenge + Parallel Old,還有
PreNew + CMS的組合也是系統常用的,兼顧了吞吐量和停頓時間的效能考慮,
或者是:
Parallel Scavenge + old serial
,對於一般請求量不大,不要求實時性的單核cpu系統也可以採用 Serial + Serial old 即可滿足需求,效率也很高。
備註:常用引數
JVM常用配置引數
配置引數 | 功能 |
---|---|
-Xms | 初始堆大小。如:-Xms4g |
-Xmx | 最大堆大小。如:-Xmx4g |
-Xmn | 新生代大小。通常為 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 個 Survivor 空間。實際可用空間為 = Eden + 1 個 Survivor,即 90% |
-Xss | JDK1.5+ 每個執行緒堆疊大小為 1M,一般來說如果棧不是很深的話, 1M 是絕對夠用了的。 |
-XX:NewRatio | 新生代與老年代的比例,如 –XX:NewRatio=2,則新生代佔整個堆空間的1/3,老年代佔2/3 |
-XX:SurvivorRatio | 新生代中 Eden 與 Survivor 的比值。預設值為 8。即 Eden 佔新生代空間的 8/10,另外兩個 Survivor 各佔 1/10 |
-XX:PermSize | 永久代(方法區)的初始大小 |
-XX:MaxPermSize | 永久代(方法區)的最大值 |
GC日誌列印引數參考
gc日誌列印引數 |
|
參考資料:
《深入瞭解jvm虛擬機器》
https://blog.csdn.net/qq_31156277/article/details/79962445
https://www.cnblogs.com/yang-hao/p/5936059.html
https://www.cnblogs.com/ASPNET2008/p/6496481.html
https://www.cnblogs.com/yunxitalk/p/8987318.html
https://blog.csdn.net/qq_31156277/article/details/79951819
https://blog.csdn.net/hutongling/article/details/69908443
相關文章
- JVM 垃圾回收機制JVM
- JVM垃圾回收機制JVM
- JVM學習(二)——GC垃圾回收機制JVMGC
- [效能][JVM]jvm垃圾回收機制JVM
- jvm的垃圾回收機制JVM
- JVM垃圾回收機制入門JVM
- 談談 JVM 垃圾回收機制JVM
- 聊聊JVM的垃圾回收機制GCJVMGC
- 秒懂JVM的垃圾回收機制JVM
- JVM之垃圾回收機制詳解分析JVM
- 深入理解 JVM 之 垃圾回收機制JVM
- JVM虛擬機器-垃圾回收機制與垃圾收集器概述JVM虛擬機
- Java教程分享:JVM垃圾回收機制之物件回收演算法JavaJVM物件演算法
- java垃圾回收機制Java
- js垃圾回收機制JS
- javascript 垃圾回收機制JavaScript
- Python垃圾回收機制Python
- Java 垃圾回收機制Java
- JVM垃圾回收JVM
- [JVM]垃圾回收JVM
- 面試準備之JVM的組成、垃圾回收機制面試JVM
- JVM必備基礎知識(三)-- GC垃圾回收機制JVMGC
- 一文帶你瞭解 JVM 的垃圾回收機制JVM
- JVM組成以類載入方式(雙親委派機制)、jvm垃圾回收JVM
- 剖析垃圾回收機制(上)
- java垃圾回收機制整理Java
- JS的垃圾回收機制JS
- JavaScript的垃圾回收機制JavaScript
- PHP的垃圾回收機制PHP
- JVM垃圾回收概述JVM
- JVM垃圾回收器JVM
- JVM垃圾回收(下)JVM
- JVM - 垃圾回收概述JVM
- PHP的垃圾回收機制-回收週期PHP
- JAVA垃圾回收機制和Python垃圾回收對比與分析JavaPython
- JS垃圾回收機制筆記JS筆記
- V8垃圾回收機制
- 【翻譯】PHP 垃圾回收機制PHP
- Flutter中的垃圾回收機制Flutter