按線程數分,能夠分爲串行垃圾回收器和並行垃圾回收器html
按照工做模式分,能夠分爲併發式垃圾回收器和獨佔式垃圾回收器java
按碎片處理方式分,可分爲壓縮式垃圾回收器和非壓縮式垃圾回收器git
按工做的內存區間分,又可分爲年輕代垃圾回收器和老年代垃圾回收器github
==吞吐量:運行用戶代碼的時間佔總運行時間的比例==web
垃圾收集開銷:吞吐量的補數,垃圾收集所用時間與總運行時間的比例。面試
==暫停時間:執行垃圾收集時,程序的工做線程被暫停的時間==算法
收集頻率:相對於應用程序的執行,收集操做發生的頻率。windows
==內存佔用: Java堆區所佔的內存大小==bash
快速:一個對象從誕生到被回收所經歷的時間。服務器
這三者共同構成一個「不可能三角」。三者整體的表現會隨着技術進步而愈來愈好。一款優秀的收集器一般最多同時知足其中的兩項。
這三項裏,暫停時間的重要性日益凸顯。由於隨着硬件發展,內存佔用 多些愈來愈能容忍,硬件性能的提高也有助於下降收集器運行時對應用程序的影響,即提升了吞吐量。而內存的擴大,對延遲反而帶來負面效果。
簡單來講,主要抓住兩點:
垃圾收集機制是Java的招牌能力,極大地提升了開發效率。這固然也是面試的熱點。
那麼,Java常見的垃圾收集器有哪些?
有了虛擬機,就必定須要收集垃圾的機制,這就是Garbage Collection, 對應的產品咱們稱爲Garbage Collector.
查看默認的垃圾收集器
/**
* -XX:+PrintCommandLineFlags
*
* -XX:+UseSerialGC:代表新生代使用Serial GC ,同時老年代使用Serial Old GC
*
* -XX:+UseParNewGC:標明新生代使用ParNew GC
*
* -XX:+UseParallelGC:代表新生代使用Parallel GC
* -XX:+UseParallelOldGC : 代表老年代使用 Parallel Old GC
* 說明:兩者能夠相互激活
*
* -XX:+UseConcMarkSweepGC:代表老年代使用CMS GC。同時,年輕代會觸發對ParNew 的使用
*/
public class GCUseTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
while(true){
byte[] arr = new byte[100];
list.add(arr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
複製代碼
輸出
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
複製代碼
或命令行
jdk8 使用的是parallel
jdk9 使用的是G1
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC
若是說Serial GC是年輕代中的單線程垃圾收集器,那麼ParNew收集器則是Serial收集器的多線程版本。
ParNew收集器除了採用並行回收的方式執行內存回收外,兩款垃圾收集器之間幾乎沒有任何區別。ParNew收集器在年輕代中一樣也是採用複製算法、"Stop一 the一World"機制。
ParNew是不少JVM運行在Server模式下新生代的默認垃圾收集器。
對於新生代,回收次數頻繁,使用並行方式高效。
對於老年代,回收次數少,使用串行方式節省資源。(CPU並行 須要切換線程,串行能夠省去切換線程的資源)
因爲ParNew收集器是基於並行回收,那麼是否能夠判定ParNew收集器的回收效率在任何場景下都會比Serial收集器更高效?| I
由於除Serial外,目前只有ParNew GC能與CMS收集器配合工做
在程序中,開發人員能夠經過選項"一XX: +UseParNewGC"手動指定使用.ParNew收集器執行內存回收任務。它表示年輕代使用並行收集器,不影響老年代。
一XX:ParallelGCThreads 限制線程數量,默認開啓和CPU數據相同的線程數。.
CMS整個過程比以前的收集器要複雜,整個過程分爲4個主要階段,即初始標記階段、併發標記階段、從新標記階段和併發清除階段。
儘管CMS收集器採用的是併發回收(非獨佔式),可是在其初始化標記和再次標記這兩個階段中仍然須要執行「Stop一the一World」機制暫停程序中的工做線程,不過暫停時間並不會太長,所以能夠說明目前全部的垃圾收集器都作不到徹底不須要「Stop一the一World」,只是儘量地縮短暫停時間。
因爲最耗費時間的併發標記與併發清除階段都不須要暫停工做,因此總體的回收是低停頓的。
另外,因爲在垃圾收集階段用戶線程沒有中斷,因此在CMS回收過程當中,還應該確保應用程序用戶線程有足夠的內存可用。所以,CMS收集器不能像其餘收集器那樣等到老年代幾乎徹底被填滿了再進行收集,而是當堆內存使用率達到某一閾值時,便開始進行回收,以確保應用程序在CMS工做過程當中依然有足夠的空間支持應用程序運行。要是CMS運行期間預留的內存沒法知足程序須要,就會出現一次「Concurrent Mode Failure」失敗,這時虛擬機將啓動後備預案:臨時啓用Serial 0ld收集器來從新進行老年代的垃圾收集,這樣停頓時間就很長了。
CMS收集器的垃圾收集算法採用的是標記一清除算法,這意味着每次執行完內存回收後,因爲被執行內存回收的無用對象所佔用的內存空間極有多是不連續的一些內存塊,不可避免地將會產生一些內存碎片。 那麼CMS在爲新對象分配內存空間時,將沒法使用指針碰撞(Bump the Pointer) 技術,而只可以選擇空閒列表(Free List) 執行內存分配。
有人會以爲既然Mark Sweep會形成內存碎片,那麼爲何不把算法換成Mark Compact呢?
答案其實很簡答,由於當併發清除的時候,用Compact整理內存的話,原來的用戶線程使用的內存還怎麼用呢?要保證用戶線程能繼續執行,前提的它運行的資源不受影響嘛。Mark Compact更適合「Stop the World」這種場景」下使用
HotSpot有這麼多的垃圾回收器,那麼若是有人問,Serial GC、 Parallel GC、Concurrent Mark Sweep GC這三個GC有什麼不一樣呢?
請記住如下口令:
若是你想要最小化地使用內存和並行開銷,請選Serial GC;
若是你想要最大化應用程序的吞吐量,請選Parallel GC;
若是你想要最小化GC的中斷或停頓時間,請選CMS GC。
既然咱們已經有了前面幾個強大的GC,爲何還要發佈Garbage First (G1)GC?
緣由就在於應用程序所應對的業務愈來愈龐大、複雜,用戶愈來愈多,沒有GC就不能保證應用程序正常進行,而常常形成STW的GC又跟不上實際的需求,因此纔會不斷地嘗試對GC進行優化。G1 (Garbage一First) 垃圾回收器是在Java7 update4以後引入的一個新的垃圾回收器,是當今收集器技術發展的最前沿成果之一。
與此同時,爲了適應如今不斷擴大的內存和不斷增長的處理器數量,進一步下降暫停時間(pause time) ,同時兼顧良好的吞吐量。
==官方給G1設定的目標是在延遲可控的狀況下得到儘量高的吞吐量,因此才擔當起「全功能收集器」的重任與指望==
爲何名字叫作Garbage First (G1)呢?
與其餘GC收集器相比,G1使用了全新的分區算法,其特色以下所示:
G1的設計原則就是簡化JVM性能調優,開發人員只須要簡單的三步便可完成調優:
G1中提供了三種垃圾回收模式: YoungGC、 Mixed GC和Full GC, 在不一樣的條件下被觸發。
使用G1收集器時,它將整個Java堆劃分紅約2048個大小相同的獨立Region塊,每一個Region塊大小根據堆空間的實際大小而定,總體被控制在1MB到32MB之間,且爲2的N次冪,即1MB, 2MB, 4MB, 8MB, 1 6MB, 32MB。能夠經過一 XX:G1HeapRegionSize設定。全部的Region大小相同,且在JVM生命週期內不會被改變。
雖然還保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔離的了,它們都是一部分Region (不須要連續)的集合。經過Region的動態分配方式實現邏輯_上的連續。
G1 GC的垃圾回收過程主要包括以下三個環節:
當愈來愈多的對象晉升到老年代oldregion時,爲了不堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器,即Mixed GC, 該算法並非一個0ldGC,除了回收整個Young Region,還會回收一部分的0ldRegion。這裏須要注意:是一部分老年代, 而不是所有老年代。能夠選擇哪些0ldRegion進行收集,從而能夠對垃圾回收的耗時時間進行控制。也要注意的是Mixed GC並非Fu1l GC。
G1的初衷就是要避免Full GC的出現。可是若是上述方式不能正常工做,G1會中止應用程序的執行(Stop一 The一World),使用單線程的內存回收算法進行垃圾回收,性能會很是差,應用程序停頓時間會很長。
要避免Full GC的發生,一旦發生須要進行調整。何時會發生Full GC呢?好比堆內存過小,當G1在複製存活對象的時候沒有空的內存分段可用,則會回退到full gc, 這種狀況能夠經過增大內存解決。
致使G1Full GC的緣由可能有兩個:
從Oracle官方透露出來的信息可獲知,回收階段(Evacuation)其實.本也有想過設計成與用戶程序一塊兒併發執行,但這件事情作起來比較複雜,考慮到G1只是回收一部分Region, 停頓時間是用戶可控制的,因此並不迫切去實現,而選擇把這個特性放到了G1以後出現的低延遲垃圾收集器(即ZGC)中。另外,還考慮到G1不是僅僅面向低延遲,停頓用戶線程可以最大幅度提升垃圾收集效率,爲了保證吞吐量因此才選擇了徹底暫停用戶線程的實現方案。
截止JDK 1.8,一共有7款不一樣的垃圾收集器。每一款不一樣的垃圾收集器都有不一樣的特色,在具體使用的時候,須要根據具體的狀況選用不一樣的垃圾收集器。
不一樣廠商、不一樣版本的虛擬機實現差異很大。HotSpot 虛擬機在JDK7/8後全部收集器及組合(連線),以下圖:
怎麼選擇垃圾回收器
經過閱讀GC日誌,咱們能夠了解Java虛擬機內存分配與回收策略。內存分配與垃圾回收的參數列表
[GC (Allocation Failure) 80832K一>19298K(227840K),0.0084018 secs]
[GC (Metadata GC Threshold) 109499K一>21465K (228352K),0.0184066 secs]
[Full GC (Metadata GC Threshold) 21 465K一>16716K (201728K),0.0619261 secs ]
複製代碼
GC、Full GC: GC的類型,GC只在新生代上進行,Full GC包括永生代,新生代, 老年代。
Allocation Failure: GC發生的緣由。
80832K一> 19298K:堆在GC前的大小和GC後的大小。
228840k:如今的堆大小。
0.0084018 secs: GC持續的時間。
複製代碼
-打開GC日誌: 一verbose:gc一 XX: +PrintGCDetaiis
[GC (Allocation Failure) [ PSYoungGen: 70640K一> 10116K(141312K) ] 80541K一>20017K (227328K),0.0172573 secs] [Times: user=0.03 sys=0.00, real=0.02 secs ]
[GC (Metadata GC Threshold) [PSYoungGen:98859K一>8154K(142336K) ] 108760K一>21261K (228352K),
0.0151573 secs] [Times: user=0.00 sys=0.01, real=0.02 secs]
[Full GC (Metadata GC Threshold) [PSYoungGen: 8154K一>0K(142336K) ] [ParOldGen: 13107K一>16809K(62464K) ] 21261K一>16809K (204800K),[Metaspace: 20599K一>20599K (1067008K) ],0.0639732 secs]
[Times: user=0.14 sys=0.00, real=0.06 secs]
複製代碼
GC,Full FC:一樣是GC的類型
Allocation Failure: GC緣由
PSYoungGen:使用了Parallel Scavenge並行垃圾收集器的新生代GC先後大小的變化
ParOldGen:使用了Parallel Old並行垃圾收集器的老年代Gc先後大小的變化
Metaspace: 元數據區GC先後大小的變化,JDK1.8中引入了 元數據區以替代永久代
xxx secs : 指Gc花費的時間
Times: user: 指的是垃圾收集器花費的全部CPU時間,sys: 花費在等待系統調用或系統事件的時間, real :GC從開始到結束的時間,包括其餘進程佔用時間片的實際時間。
複製代碼
一verbose:gc 一XX: +PrintGCDetails 一XX:+PrintGCTimeStamps 一 XX: +PrintGCDateStamps
2019一09一24T22:15:24.518+0800:3.287: [GC(Allocation Failure) [ PSYoungGen: 1361 62K一>5113K(136192K) ] 141425K一>17632K (222208K) ,0.0248249 secs] [Times: user=0.05sys=0.00, real=0.03 secs ]
2019一09一24T22:15:25.559+0800:4.329: [ GC(Metadata GC Threshold)[PSYoungGen:97578K一>10068K(274944K) ] 110096K一>22658K (360960K),0.0094071 secs]
[Times: user=0. 00sys=0.00, real=0. 01 secs]
2019一09一24T22:15:25.569+0800:4.338: [Full GC (Metadata GC Threshold)[ PSYoungGen:10068K一>0K(274944K) ] [ ParoldGen: 12590K一>13564K (56320K) ] 22658K一>13564K (331264K) ,
[Metaspace: 20590K一>20590K(1067008K)], 0. 0494875 secs]
[Times: user=0.17 sys=0. 02,real=0.05 secs ]
複製代碼
說明:帶上了日期和時間
Minor GC
Full GC
/**
* 在jdk7 和 jdk8中分別執行
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
*/
public class GCLogTest1 {
private static final int _1MB = 1024 * 1024;
public static void testAllocation() {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB];
}
public static void main(String[] agrs) {
testAllocation();
}
}
複製代碼
能夠用一些工具去分析這些gc日誌。
經常使用的日誌分析.工具備: GCViewer、GCEasy、GCHisto、GCLogViewer 、Hpjmeter、garbagecat等。
GC仍然處於飛速發展之中,目前的默認選項G1 GC在不斷的進行改進,不少咱們原來認爲的缺點,例如串行的Full GC、Card Table掃描的低效等,都已經被大幅改進,例如,JDK 10之後,Fu1l GC已是並行運行,在不少場景下,其表現還略優於Parallel GC的並行Full GC實現。
即便是Serial GC,雖然比較古老,可是簡單的設計和實現未必就是過期的,它自己的開銷,無論是GC相關數據結構的開銷,仍是線程的開銷,都是很是小的,因此隨着雲計算的興起,在Serverless等新的應用場景下,Serial GC找到了新的舞臺。
比較不幸的是CMS GC, 由於其算法的理論缺陷等緣由,雖然如今還有很是大的用戶羣體,但在JDK9中已經被標記爲廢棄,並在JDK14版本中移除。
Open JDK12 的Shenandoah GC:低停頓時間的GC (實驗性)
官網連接
ZGC與Shenandoah目標高度類似,在儘量對吞吐量影響不大的前提下,實如今任意堆內存大小下均可以把垃圾收集的停頓時間限制在十毫秒之內的低延遲。
《深刻理解Java虛擬機》一書中這樣定義ZGC: ZGC收集器是一款基於Region內存佈局的,(暫時) 不設分代的,使用了讀屏障、染色指針和內存多重映射等技術來實現可併發的標記一壓縮算法的,以低延遲爲首要目標的一款垃圾收集器。
ZGC的工做過程能夠分爲4個階段:併發標記一併發預備重分配一併發重分配一併發重映射等。
ZGC幾乎在全部地方併發執行的,除了初始標記的是sTW的。因此停頓時間.幾乎就耗費在初始標記上,這部分的實際時間是很是少的。
測試數據如圖:
劣勢比較
優點比較
在ZGC的強項停頓時間測試上,它絕不留情的將Parallel、G1拉開了兩個數量級的差距。不管平均停頓、958停頓、998停頓、99. 98停頓,仍是最大停頓時間,ZGC 都能絕不費勁控制在10毫秒之內。
JEP 364: ZGC應用在macOS上
JEP 365: ZGC應用在windows上 JDK14以前,ZGC僅Linux才支持
AliGC是阿里巴巴JVM團隊基於G1算法,面 向大堆(LargeHeap)應用場景。指定場景下的對比: 固然,其餘廠商也提供了各類獨具一格的GC實現,例如比較有名的低延遲GC,Zing ( www.infoq.com/articles/az…)
【代碼】
github.com/willShuhuan…
【筆記】
JVM_01 簡介
JVM_02 類加載子系統
JVM_03 運行時數據區1- [程序計數器+虛擬機棧+本地方法棧]
JVM_04 本地方法接口
JVM_05 運行時數據區2-堆
JVM_06 運行時數據區3-方法區
JVM_07 運行時數據區4-對象的實例化內存佈局與訪問定位+直接內存
JVM_08 執行引擎(Execution Engine)
JVM_09 字符串常量池StringTable
JVM_10 垃圾回收1-概述+相關算法
JVM_11 垃圾回收2-垃圾回收相關概念
JVM_12 垃圾回收3-垃圾回收器