JVM垃圾收集器整理
概述
垃圾收集器是jvm實現內存回收的具體實現。本次分享要介紹的7種垃圾收集器的作用區域及其之間的關系如下圖:
註:
- 如果2個垃圾收集器之間有連線,表示可以搭配使用
- 垃圾收集器並沒有最好的,只有針對不同應用場景最合適的
(1)Serial收集器
算法 | 內存區域 | 執行方式 |
---|---|---|
復制算法 | 新生代 | 單線程、串行 |
過程
先暫停全部用戶線程(Stop The World),然後開啟一條GC線程使用復制算法對垃圾進行回收。直到收集結束被暫停的線程才能繼續執行
特點
- 簡單而高線,對單CPU環境沒有線程交互的開銷。
- 由於單線程運行,且整個GC階段都要暫停用戶線程,因此會造成應用程序停頓。
- client模式下的默認垃圾收集器
使用場景
平時的開發與調試程序使用,以及桌面應用交互程序。
(2)Serial Old收集器
算法 | 內存區域 | 執行方式 |
---|---|---|
標記/整理算法 | 老年代 | 單線程、串行 |
執行過程、特點、使用場景與Serial收集器類似
(3)ParNew收集器
算法 | 內存區域 | 執行方式 |
---|---|---|
復制算法 | 新生代 | 多線程、並行 |
過程
開啟多個GC線程使用復制算法並行進行垃圾回收
特點
- 使用多個線程去並行執行垃圾回收,在發揮多處理器的優勢
- 在單處理器上的性能會低於Serial收集器(線程交互開銷)
- 執行垃圾回收也需要暫停其他用戶線程
使用場景
在中到大型的堆上,且系統處理器至少多於一個的情況。
(4)Parallel Scavenge收集器
算法 | 內存區域 | 執行方式 |
---|---|---|
復制算法 | 新生代 | 並行 |
過程
ParNew收集器基本類似,只不過優先滿足最大停頓時間的目標,次之是吞吐量,最後才是新生代區域的最小值。
吞吐量=運行用戶代碼的時間/CPU總消耗時間
特點
- 更精確的控制GC停頓時間以及吞吐量
- 控制最大的停頓時間(使用-XX:MaxGCPauseMillis=),以及控制吞吐量(使用-XX:GCTimeRatio=)
使用場景
適用於與用戶交互的程序,良好的響應速度能提升用戶體驗,高吞吐量則可以高效的利用cpu。主要適合在後臺運算而不需要太多交互的任務
Parallel Old收集器
算法 | 內存區域 | 執行方式 |
---|---|---|
標記/整理算法 | 老年代 | 並行 |
特點
除了serial old以外唯一一個可以與parallel scavenge搭配工作的年老代搜集器
CMS收集器
算法 | 內存區域 | 執行方式 |
---|---|---|
標記/清除算法 | 老年代 | 多線程、並行 |
過程
- 初始標記(CMS initial mark):需要用戶線程停頓。標記一下GC Roots能直接關聯到的對象
- 並發標記(CMS concurrent mark):以初始標記的對象為root進行並發標記。
- 重新標記(CMS remark):需要用戶線程停頓,是為了修正並發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄。
- 並發清除(CMS concurrent sweap):清除標記的對象
特點
1.會有2次Stop The World
1.初始標記階段,時間很短,只會標記直接與GC Root相連的對象 2.重新標記階段,時間比初始化標記稍長
2.浮動垃圾
產生原因
在進行標記階段,標記的對象不再從GC Root可達,因此在這次GC過程中該對象內存空間不會被回收,因此造成了浮動垃圾。
影響
這些浮動垃圾會占用一些內存,但下次GC會被回收。
3.三色標記法
產生原因
在並發標記階段,GC線程沒有標記(用戶線程之前沒在用),在標記過程中,用戶線程又重新引用了該對象。如果該對象依然被回收,那麽程序就直接報錯了。
標記過程
下面舉個例子說明,設計A是被GC Root相關聯的對象,假設現在正在搜索A相關聯的對象,這個時候A被標記為灰色,搜索到A引用了B,這個時候B也會被標記為灰色,如下圖
當A搜索完以後,A被標記為綠色,這個時候直接與A相連的B和C已被標記為灰色,但D沒有,因為D沒有直接與A相連,如下圖
然後繼續搜索B直接引用的對象,假設這個過程中,C不再引用D,如下圖
如果在搜索C相關的引用後,D依然處於不可達的狀態,假設這個時候已經標記完成的對象B引用了D,如下圖
如果D被回收,那麽在後面的程序肯定會有問題
解決辦法
此時的解決辦法就是有一個叫做寫入屏障(註意在G1收集器中會比較)的東西。就是說,如果B已經被標記了(已經是綠色的了),那麽用戶線程改動 B->D的時候,會把D變成灰色,這樣,以後就可以搜索D了。
4.Concurrent Mode Failure
由於其他垃圾回收器都是 "stop the world",那麽內存不夠了就執行 GC。但是 CMS 垃圾回收器是可以和用戶線程一起並發的。假設在執行GC過程中內存不夠了怎麽辦?這個時候就會有預備方案,Serial Old 垃圾回收器會替代 CMS,進行 "stop the world"垃圾收集。
使用-XX:CMSInitiatingOccupancyFraction的值來設定觸發GC的內存百分比
5.空間碎片
CMS是一款基於“標記-清除”算法實現的收集器,收集結束時會有大量的空間碎片產生。當無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次full Gc。
-XX:+UseCMSCompactAtFullCollection
默認是打開的,用戶在CMS收集器頂不要進行Full GC時開啟內存碎片的合並整理過程,內存整理的過程是無法並發的,空間碎片的問題沒有了,但停頓的時間會變長
-XX:CMSFullGCBeforeCompaction
設置多少次不壓縮Full GC後,跟著來一次帶壓縮的碎片整理,默認為0。
6.吞吐量影響
CMS收集器對CPU資源非常敏感.CMS默認啟動的回收線程數是(CPU數量+3)/4,在並發階段,它雖然不會導致用戶線程停頓,但是會占用一部分線程而導致應用程序變慢,總吞吐量對降低。
使用場景
重視服務的響應速度,希望系統的停頓時間最短,以帶來較好的用戶體驗,例如互聯網站或B/S系統的服務端上,
G1收集器
CMS垃圾收集器雖然減少了暫停應用程序的運行時間,但是它還是存在著內存碎片問題。於是,為了去除內存碎片問題,同時又保留CMS垃圾收集器低暫停時間的優點,JAVA7發布了一個新的垃圾收集器 - G1垃圾收集器。
內存組織方式變更
Eden,Survivor和Tenured等內存區域不再是連續的了,而是變成了一個個大小一樣的region - 每個region從1M到32M不等。一個region有可能屬於Eden,Survivor或者Tenured內存區域。 在G1中,還有一種特殊的區域,叫Humongous區域。 如果一個對象占用的空間超過了分區容量50%以上,G1收集器就認為這是一個巨型對象。G1劃分了一個Humongous區,它用來專門存放巨型對象。如果一個H區裝不下一個巨型對象,那麽G1會尋找連續的H分區來存儲。為了能找到連續的H區,有時候不得不啟動Full GC。
新生代收集
在G1垃圾收集器中CMS垃圾收集器差不多,回收Eden region和Survivor region上的非可達對象,同時升級存活的可達對象到對應的Survivor region或Tenured region上。
老年代收集
對於年老代上的垃圾收集,G1垃圾收集器也分為4個階段,基本跟CMS垃圾收集器一樣,但略有不同:
- 初始化標記:過程和CMS基本類似,不同的是該階段在觸發YGC的時候執行(有什麽好處?)
- 並發標記階段:和CMS基本類似,不同的是在該階段,G1會計算每個 region的對象存活率,方便後面的clean up階段使用。
- 重新標記:和CMS基本類似,但采取算法不一樣,G1采用一種叫做SATB(snapshot-at-the-begining)的算法能夠在重新階段更快的標記可達對象。(CMS是使用寫入屏障)
- Clean up/Copy階段 - 在G1中,沒有CMS中對應的並發清楚階段。相反 它有一個Clean up/Copy階段,在這個階段中,G1會挑選出那些對象存活率低的region進行回收,這個階段也是和minor gc一同發生的,如下圖所示
JVM垃圾收集器整理