本文主要介紹了JVM—GC垃圾回收器,並對其進行了總結。主要包含了Serial收集器、ParNew收集器、Parallel Scavenge收集器、Servial Old收集器、Parallel Old收集器、CMS收集器、G1收集器。 ...
收集演算法(標記-清理、複製、標記-整理、分代收集)是記憶體回收的方法論,垃圾收集器就是記憶體回收的具體實現。
主要有7個gc器,如下圖。
1 Serial收集器
1.1 介紹
Serial收集器是單線程的收集器。
單線程:1.不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作,2.且在垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。
Stop the world:是VM在後臺自動發起和自動完成的,在用戶不可見情況下把用戶正常工作的線程全部停掉。
1.2 缺點:
由於Stop The World,給用戶帶來不良體驗,比如,電腦每運行一段時間就會暫停響應幾分鐘來處理垃圾收集。
1.3 優點
1)簡單而高效(與其他收集器的單線程比);
2)對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。
1.4 應用場景
1)VM運行在Client模式下的預設新生代收集器;
2)在用戶的桌面應用場景中,停頓時間完全可以控制在幾十毫秒最多一百多毫秒以內,不頻繁發生,是可接受的
1.5 Serial/Serial Old收集器運行示意圖
2 ParNew收集器
2.1 介紹
ParNew收集器是Serial收集器多線程版本(是GC線程的多線程,並行)。
• 並行:Parallel指多條垃圾收集線程並行工作,但此時用戶線程仍然處於等待狀態(多個處理器同時處理多條指令);
• 併發:Concurrent指用戶線程與垃圾收集線程同時執行(但並不一定是並行的,可能交替執行),用戶程式在繼續運行,而垃圾收集程式運行於另一個CPU上(同一時刻只能有一條指令執行,多個進程指令是交替執行)。
2.2 缺點
在單CPU的環境中絕對不會有比Serial收集器更好的效果,甚至存線上程交互的開銷。
2.3 優點
1)除了Serial收集器外,只有ParNew收集器能與CMS收集器配合工作。
2)CMS(Concurrent Mark Sweep)第一次實現讓垃圾收集線程與用戶線程(基本上)同時工作。
2.4 應用場景
運行在Server模式下的VM首選新生代收集器。
2.5 ParNew/Serial Old收集器運行示意圖
2.6 參數控制
• 使用-XX:+UseConcMarkSweepGC選項後預設新生代收集器為ParNew收集器;
• 使用-XX:+UseParNewGC選項強制指定使用ParNew收集器;
• 使用-XX:ParallelGCThreads參數限制垃圾收集的線程數;
3 Parallel Scavenge收集器
3.1 介紹
1)Parallel Scavenge收集器是一個新生代收集器,使用複製演算法的收集器,並行的多線程收集器。更關註吞吐量。
2)吞吐量:CPU用於運行用戶代碼的時間與cpu總消耗時間的比值,即
如虛擬機總共運行100分鐘,垃圾收集花費1分鐘,則吞吐量是99%;吞吐量高效率利用CPU時間,儘快完成程式的運算任務,主要適合後臺運算而不需要太多交互的任務。
3)停頓時間:如CMS等收集器關註點儘可能縮短垃圾收集時用戶線程的停頓時間,停頓時間越短就越適合需要與用戶交互的程式,良好的響應速度可以提升用戶體驗(適合交互)
3.2 參數控制
1)用戶精確控制吞吐量
• 使用-XX:MaxGCPauseMillis參數:控制最大垃圾收集停頓時間
• 使用-XX:GCTimeRatio參數:直接設置吞吐量大小
• 使用-XX:+UseAdaptiveSizePolicy開關參數:GC自適應的調節策略
2)MaxGCPauseMillis參數允許的值是一個大於0的毫秒數,收集器儘可能保證記憶體回收時間不超過設定值。
GC停頓時間縮短犧牲吞吐量和新生代空間——若將MaxGCPauseMillis該值調小帶來的問題:系統把新生代調小一些,收集發生更頻繁一些,吞吐量下降。
GCTimeRatio參數值是一個大於0且小於100的整數,即垃圾收集時間占總時間的比率,相當於吞吐量的倒數。如設置為19,則最大GC時間占1/(1+19)=5%,預設值為99.則最大允許1/(1+99)=1%的垃圾收集時間。
UseAdaptiveSizePolicy開關參數:VM會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大吞吐量。自適應調節策略是Parallel Scavenge收集器與ParNew收集器的重要區別。
3.3 應用場景
主要適合後臺運算而不需要太多交互的任務
4 Serial Old收集器
4.1 介紹
Serial Old收集器是Serial收集器的老年代版本,是一個單線程收集器,使用“標記-整理”演算法。
4.2 應用場景
1)主要給Client模式下的VM使用。
2)若在Server模式下用,兩大用途:1.在JDK1.5及之前的版本中與Parallel Scavenge收集器搭配使用;2.作為CMS收集器備選,併在Concurrent Mode Failure時使用。
4.3 Serial/Serial Old收集器運行示意圖
5 Parallel Old收集器
5.1 介紹
Parallel Old是Parallel Scavenge收集器的老年代版本,是一個多線程收集器,使用“標記-整理”演算法。在JDK1.6開始提供。
5.2 應用場景
註重吞吐量以及CPU資源敏感的場合,優先考慮Parallel Scavenge + Parallel Old收集器。適合吞吐量優先。
5.3 Parallel Scavenge /Parallel Old收集器運行示意圖
6 CMS收集器
6.1 介紹
CMS收集器(Concurrent Mark Sweep)是一種以獲取最短回收停頓時間為目標的收集器,是基於“標記-清除”演算法。
6.2 CMS的整個過程有4個步驟
初始標記——併發標記——重新標記——併發清除
• 初始標記:CMS initial mark僅僅是標記一下GC Roots能直接關聯的對象,速度快;需要stop the world
• 併發標記:CMS concurrent mark是進行GC Roots Tracing的過程;
• 重新標記:CMS remark是修正併發標記期間因用戶程式繼續運作而導致標記產生變動的那一部分對象的標記記錄,停頓時間比初始標記長,比併發標記短;需要stop the world
• 併發清除:CMS concurrent sweep,清除演算法會在收集結束時產生大量空間碎片,有可能導致沒有足夠大的連續空間來分配當前對象而觸發一次Full GC。
6.3 缺點
1)CMS收集器對CPU資源非常敏感;
2)CMS收集器無法處理浮動垃圾,可能出現"Concurrent Mode Failure"失敗(備選用Serial Old)而導致另一次Full GC的產生;
3)CMS是一款基於“標記-清除”演算法的收集器,在收集結束後會產生大量空間碎片。
缺點具體分析
1)對CPU資源敏感:在併發階段會占用一部分線程而導致應用程式變慢,總吞吐量降低;(解決方法是“增量式併發收集器”,但不提倡使用,i-CMS收集器是與單CPU年代PC機操作系統使用搶占式模擬多任務機制的思想,在併發標記、清理的時候讓GC線程、用戶線程交替執行,儘量減少GC線程的獨占資源的時間)
2)無法處理浮動垃圾:CMS併發清理階段用戶線程還在運行,會產生新的垃圾,這部分垃圾出現在標記過程之後,CMS無法在當次收集中處理它們,只好留到下一次GC時再處理。CMS需要預留一部分提供併發收集時的程式運行使用,CMS收集時老年代不能填滿再收集。
3)收集後產生大量空間碎片:“標記-清除”演算法的缺點,解決方案是使用-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction參數
6.4 優點
併發收集;低停頓(併發低停頓收集器)
6.5 應用場景
在互聯網站或者B/S系統的服務端上,重視服務的響應速度,希望系統停頓時間最短,給用戶帶來較好的體驗。
6.6 參數控制
- 使用-XX:CMSInitiatingOccupancyFraction參數:提高觸發老年代CMS垃圾回收的百分比;
- 使用-XX:+UseCMSCompactAtFullCollection開關參數:預設開啟,用於CMS收集器要進行Full GC時開啟記憶體碎片合併整理過程,非併發的過程;
- 使用-XX:CMSFullGCsBeforeCompaction參數:用於設置執行多少次不壓縮的Full GC後,緊接著一次帶壓縮的(預設為0,表示每次進入Full GC時就進行碎片整理)
6.7 CMS收集器運行時示意圖
7 G1收集器
7.1 介紹
G1(Garbage-First)收集器是一款面向服務端應用的垃圾收集器,為了代替JDK1.5中發佈的CMS收集器。將整個Java堆劃分為多個大小相等的獨立區域。####(Region),保留新生代和老年代概念,但不再是物理隔離,是一部分Region的集合(不需要連續)。
7.2 優點
併發與並行、分代收集、空間整合、可預測的停頓
• 併發與並行:G1能充分利用多CPU、多核環境下的硬體優勢,使用多個CPU縮短storp-the-world停頓時間;G1可通過併發的方式使得java程式運行;
• 分代收集:可以獨立管理整個GC堆,採用不同的方式處理新創建的對象和已經存活一段時間、熬過多次GC的舊對象以獲取更好的收集效果;
• 空間整合:整體上基於“標記-整理”演算法,局部(兩個Region之間)基於“複製”演算法,G1運行期間不會產生記憶體空間碎片,收集後能提供規整的可用記憶體,有利於程式長時間運行,分配大對象時不會因為無法獲得連續記憶體空間而提前觸發下一次GC;
• 可預測的停頓:相比於CMS的另一優勢,能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。因為可以有計劃地避免在整個java堆上進行全區域的垃圾收集。G1跟蹤各個Region內垃圾堆積的價值大小(回收所獲得的空間大小+回收所需時間的經驗值),在後臺維護一個優先列表,根據允許的收集時間,回收價值最大的Region(Garbage-First的由來)。
7.3 G1將記憶體“化整為零”的思路
Region之間的對象引用以及其他收集器中的新生代與老年代之間的對象引用,VM都是通過Remember Set來避免全堆掃描。G1中每個Region中都有一個與之對應的Remember Set:
1)VM發現程式對Reference類型的數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作;
2)檢查Reference引用的對象是否處於不同的Region之中;如果是,便通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remember Set之中;
3)當進行記憶體回收時,在GC根節點的枚舉範圍中加入Remember Set即可保證不對全堆掃描;
(4)G1收集器運作的步驟:
初始標記——併發標記——最終標記——篩選回收
- 初始標記:initial marking,標記一下GC Roots能直接關聯的對象,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程式併發運行時,能在正確可用的Region中創建新對象,需要停頓線程,耗時短;
- 併發標記:concurrent marking,從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時長,但可與用戶程式併發執行;
- 最終標記:final marking,修正在併發標記期間因用戶程式繼續運作而導致標記產生變動的那一部分標記記錄,對象變化記錄存線上程Remember Set Logs中,然後把這些數據合併到Remember Set中,該階段停頓線程,但是可並行執行;
篩選回收:live data counting and evacuation,對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來指定回收計劃。
(5)G1收集器運行示意圖:
8 安全點
8.1 介紹
1)Safepoint:在HotSpot的實現中,使用一組稱為OopMap的數據結構,在類載入完成的時候,HotSpot把對象帶內什麼偏移量上是什麼類型的數據都計算出來,在JIT編譯過程中,也會在特定的位置記錄下棧和寄存器中哪些位置是引用,這些特定的位置就是安全點。
2)程式執行時並非在所有的地方都能停頓下來開始GC,只有到達安全點時才能暫停。
3)安全點的選定:標準是“是否具有讓程式長時間執行的特征”,不能太少以至於讓GC等待時間太長,也不能過於頻繁以至於過分增大運行時的負荷。
8.2 安全點的停頓
如何在GC發生時讓所有線程都“跑”到最近的安全點停頓?兩種方案:搶先式中斷和主動式中斷。
搶先式中斷:不需要線程的執行代碼主動去配合,在GC發生時,首先把所有線程全部中斷,如果發現有線程中斷的地方不在安全點上,就恢複線程,讓它“跑”到安全點上。(現在幾乎不用)
主動式中斷:當GC需要中斷線程的時候,不直接對線程操作,僅僅設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現中斷標誌為真時就自己中斷掛起。輪詢標誌的地方和安全點是重合的,另外再加上創建對象需要分配記憶體的地方。
8.3 安全點的作用
1)safepoint保證程式執行時,在不太長的時間內就會遇到可進入的GC的safepoint。
2)若程式不執行,則沒有分配CPU時間,如線程處於Sleep或Blocked狀態,無法響應JVM的中斷請求,此時需要安全區域解決,在一段代碼片段中,引用關係不會發生變化,在這個區域中的任意地方開始GC都是安全的。
9 垃圾收集器的參數總結
9.1 GC器的參數
9.2 GC器的使用
主要參考了《深入理解Java虛擬機》這本書,這本書是真心好,值得一讀。