在Java運行時的幾個數據區域中,程式計數器,虛擬機棧,本地方法棧3個區域隨著線程而生,隨線程而滅,因此這幾個區域的記憶體分配和回收具有確定性,不需要過多考慮垃圾回收問題,因為方法結束或者線程結束時,記憶體就回收了。但是方法區和堆區不一樣,一個介面或者實現類所需要的記憶體可能不一樣,一個方法的多個分支需要 ...
在Java運行時的幾個數據區域中,程式計數器,虛擬機棧,本地方法棧3個區域隨著線程而生,隨線程而滅,因此這幾個區域的記憶體分配和回收具有確定性,不需要過多考慮垃圾回收問題,因為方法結束或者線程結束時,記憶體就回收了。但是方法區和堆區不一樣,一個介面或者實現類所需要的記憶體可能不一樣,一個方法的多個分支需要的記憶體也可能不一樣,只有程式運行時才能知道創建哪些對象,這部分記憶體的分配和回收是動態的。
在進行垃圾回收時候,首先需要判斷哪些對象需要回收,這就涉及到回收演算法的問題。
一、垃圾回收演算法
1.標記-清除演算法
標記-清除演算法是一種最基礎的垃圾收集演算法,分為“標記”和“清除”兩步。“標記”階段標記所有需要進行垃圾回收的對象,標記完成後統一回收被標記的對象。這種演算法的不足點在於:
(1)效率問題,標記和清除兩個過程效率都不高;
(2)空間問題,標記清除後會產生大量不連續碎片,後續如果需要為較大對象分配空間,則又需觸發垃圾回收。
2.複製演算法
為瞭解決標記-清除演算法的效率問題,出現了複製演算法。這種演算法把記憶體按照容量劃分為大小相同的兩塊,每次只是用其中一塊,當這塊記憶體用完了,就把還存活的對象複製到另外一塊中,並將這塊的記憶體清理掉,然後使用另外一塊,當另外一塊記憶體用完了,再把存活的對象複製到這塊中,並清理另外一塊記憶體,依次類推。
複製演算法主要用於新生代的回收,在HotSpot虛擬機中,新生代記憶體劃分為一塊較大的Eden空間,和兩塊較小的Survivor空間,每次使用Eden空間和其中一塊Survivor空間。當進行垃圾回收時,會把Eden空間和Survivor空間中存活的對象一次性複製到另外一塊Survivor空間上,最後清理掉Eden空間和剛纔使用過的Survivor空間。HotSpot虛擬機中,預設情況下Eden空間和Survivor空間的大小比例是8:1,即Eden空間占整個新生代的80%,每次新生代中使用的空間為80%+10%=90%,閑置空間10%。
3.標記-整理演算法
複製演算法適用於那種對象存活率較低的場景,在對象存活率較高時,使用複製收集演算法意味著需要進行大量複製,會使效率降低,同時複製大量存活對象到另外一塊記憶體,意味著需要有足夠大的記憶體來保存這些對象,這勢必會降低記憶體使用率。根據老年代的特點,有人提出標記-整理演算法,和標記-清除演算法不同的是,標記整理演算法將存活的對象向一端移動,然後直接清理掉端邊界之外的記憶體。
4.分代收集演算法
目前商業虛擬機中都使用分代收集演算法。一般將Java堆分為新生代和老年代,新生代進行垃圾收集發現有大量對象死去,只有少量對象存活,那麼就使用複製演算法。老年代中對象存活率較高,使用標記-清除演算法或者標記-整理演算法。
二、垃圾收集器
垃圾收集演算法提供了記憶體回收的方法論,垃圾收集器是記憶體回收的方法論。每個廠商對垃圾收集器的實現不一樣,這裡主要討論Jdk1.7 Update 14之後的HotSpot虛擬機。這個虛擬機中包含的垃圾收集器有如下7種:
以上收集器之間如果有連線,則表明可以搭配使用,虛擬機所處區域,表示他是新生代收集器還是老年代收集器。
1.Serial收集器
Serial收集器是一種最基本的單線程收集器,這種收集器工作時,必須停止其他所有工作線程,優點在於簡單高效,但體驗很不友好,目前主要應用場合是:虛擬機運行在Client模式下的預設新生代收集。器。
2.ParNew收集器
parNew收集器是Serial收集器的多線程版本,常用參數設置:
-XX:+UseConcMarkSweepGC :設置ParNew為預設的新生代收集器;
-XX:+UseParNewGC :指定使用ParNew為年輕代收集器,強制指定;
-XX:ParallelGCThreads=n :設置收集器的線程數為n。
3.Parallel Scavenge收集器
Parallel Scavenge收集器是一個使用複製演算法的新生代收集器,這種收集器的主要目標是達到一個可控制的吞吐量(Throughput,CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間))。由於與吞吐量關係密切,故而Parallel Scavenge收集器也稱為“吞吐量優先”收集器。常用參數設置:
-XX:MaxGCPauseMillis=n :設置年輕代垃圾收集的最長時間;
-XX:GCTimeRatio=n :設置垃圾收集總可用時長的比例,和吞吐量直接相關;
-XX:+UseAdaptiveSizePolicy 自適應大小開關,配置該選項之後,每次GC後會重新計算 Eden、From 和 To 區的大小,計算依據是 GC 過程中統計的 GC 時間、吞吐量、記憶體占用量,因此設置此參數之後就不需要再設置 -XX:SurvivorRatio 、 -XX:PretenureSizeThreshold 等參數了。
4.Serial Old收集器
Serial收集器的老年版本,也是一個單線程收集器,使用的是“標記-整理”演算法,這種收集器的主要意義也是給Client模式下的虛擬機使用。
5.Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”演算法。在註重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge收集器和Parallel Old收集器的組合。
6.CMS收集器
CMS收集器是一種以獲取最短回收停頓時間為目標的收集器。它基於“標記-清除”演算法實現,運作過程相對於其他幾種收集器更複雜一些。分為以下四個過程:
(1)初始標記(CMS initial mark):標記一下CG Roots能關聯到的對象;
(2)併發標記(CMS concurrent mark):進行CG Roots Tracing的過程;
(3)重新標記(CMS remark):修正併發標記期間因用戶程式繼續運作而導致標記產生變動的那一部分對象的標記記錄。
(4)併發清理(CMS concurrent sweep)
CMS 收集器的優點在於併發收集,低停頓。其缺點在於以下三點:
(1)CMS收集器對CPU很敏感,CMS預設回收線程是(CPU數量+3)/4,當CPU在4個以上時,併發收集時垃圾收集線程不少於25%的CPU資源,並隨著CPU數量增加而下降。但是當CPU不足4個時,CMS對用戶程式的影響就會變得很大。
(2)CMS收集器無法處理浮動垃圾。由於CMS收集器併發清理階段用戶線程還在運行著,伴隨著程式運行就會有垃圾產生,這部分垃圾在標記過後,CMS收集器無法在當次收集中清理這些垃圾。
(3)由於CMS收集器是一種基於“標記-清除”演算法的收集器,這種演算法實現的收集器在收集結束後會有大量不連續碎片產生。碎片過多時會給大對象分配帶來很大麻煩,往往老年代還有很大空間剩餘,但是無法找到連續空間分配當前對象,因而不得不提前觸發Full GC。
7.G1收集器
G1收集器是一款面向服務端應用的垃圾收集器,與其他收集器相比,G1收集器具有如下優點:
(1)併發與並行:G1能充分利用多CPU,多核硬體優勢,使用多個CPU來減少停頓時間;
(2)分代收集:G1不需要其他收集器配合就能獨立管理整個堆的垃圾收集,且它能採用不同方式去處理新建對象和已經存活了一段時間,熬過多次GC的舊對象以獲得更好的收集效果。
(3)空間整合:使用G1收集器不會產生記憶體碎片,收集後能提供規整的可用記憶體。這種特性有利於程式長時間運行,分配大對象時候不會因為無法找到連續記憶體空間而提前觸發下一次GC.
(4)可預測的停頓:G1除了追求低停頓,還能建立可預測的停頓時間模型,能讓使用著指定在長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不超過N毫秒。
三、垃圾收集參數總結
參數 | 描述 |
---|---|
UseSerialGC |
虛擬機運行在Client模式下的預設值,打開此開關後,使用Serial+Serial Old的收集器組合進行記憶體回收 |
UseParNewGC | 打開此開關後,使用ParNew + Serial Old 的收集器組合進行記憶體回收 |
UseConcMarkSweepGC | 打開此開關後,使用ParNew + CMS + Serial Old 的收集器組合進行記憶體回收。Serial Old 收集器將作為CMS收集器出現Concurrent Mode Failure失敗後的後備收集器使用 |
UseParallelGC | 虛擬機運行在Server 模式下的預設值,打開此開關後,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器組合進行記憶體回收 |
UseParallelOldGC | 打開此開關後,使用Parallel Scavenge + Parallel Old 的收集器組合進行記憶體回收 |
SurvivorRatio | 新生代中Eden 區域與Survivor 區域的容量比值,預設為8,代表Eden :Survivor=8∶1 |
PretenureSizeThreshold | 直接晉升到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配 |
MaxTenuringThreshold | 晉升到老年代的對象年齡。每個對象在堅持過一次Minor GC 之後,年齡就加1,當超過這個參數值時就進入老年代 |
UseAdaptiveSizePolicy | 動態調整Java 堆中各個區域的大小以及進入老年代的年齡 |
HandlePromotionFailure | 是否允許分配擔保失敗,即老年代的剩餘空間不足以應付新生代的整個Eden 和Survivor 區的所有對象都存活的極端情況 |
ParallelGCThreads | 設置並行GC 時進行記憶體回收的線程數 |
GCTimeRatio | GC 時間占總時間的比率,預設值為99,即允許1% 的GC 時間。僅在使用Parallel Scavenge 收集器時生效 |
MaxGCPauseMillis | 設置GC 的最大停頓時間。僅在使用Parallel Scavenge 收集器時生效 |
CMSInitiatingOccupancyFraction | 設置CMS 收集器在老年代空間被使用多少後觸發垃圾收集。預設值為68%,僅在使用CMS 收集器時生效 |
UseCMSCompactAtFullCollection | 設置CMS 收集器在完成垃圾收集後是否要進行一次記憶體碎片整理。僅在使用CMS 收集器時生效 |
CMSFullGCsBeforeCompaction | 設置CMS 收集器在進行若幹次垃圾收集後再啟動一次記憶體碎片整理,僅在使用CMS 收集器時生效 |
參考資料:《深入理解Java虛擬機 JVM高級特性與最佳實踐 第2版》