JVM之垃圾回收機(jī)制

1诬垂、范圍:要回收哪些區(qū)域

在JVM五種內(nèi)存模型中悠鞍,有三個(gè)是不需要進(jìn)行垃圾回收的:程序計(jì)數(shù)器申钩、JVM棧塑煎、本地方法棧。因?yàn)樗鼈兊纳芷谑呛途€程同步的类浪,隨著線程的銷毀载城,它們占用的內(nèi)存會(huì)自動(dòng)釋放,所以只有方法區(qū)和堆需要進(jìn)行GC费就。

2诉瓦、前提:如何判斷對(duì)象已死

所有的垃圾收集算法都面臨同一個(gè)問題,那就是找出應(yīng)用程序不可到達(dá)的內(nèi)存塊力细,將其釋放睬澡,這里面得不可到達(dá)主要是指應(yīng)用程序已經(jīng)沒有內(nèi)存塊的引用了, 在JAVA中眠蚂,某個(gè)對(duì)象對(duì)應(yīng)用程序是可到達(dá)的是指:這個(gè)對(duì)象被根(根主要是指類的靜態(tài)變量煞聪,或者活躍在所有線程棧的對(duì)象的引用)引用或者對(duì)象被另一個(gè)可到達(dá)的對(duì)象引用。

2.1 引用計(jì)數(shù)算法

引用計(jì)數(shù)是最簡(jiǎn)單直接的一種方式逝慧,這種方式在每一個(gè)對(duì)象中增加一個(gè)引用的計(jì)數(shù)昔脯,這個(gè)計(jì)數(shù)代表當(dāng)前程序有多少個(gè)引用引用了此對(duì)象,如果此對(duì)象的引用計(jì)數(shù)變?yōu)?笛臣,那么此對(duì)象就可以作為垃圾收集器的目標(biāo)對(duì)象來收集云稚。

優(yōu)點(diǎn):簡(jiǎn)單,直接沈堡,不需要暫停整個(gè)應(yīng)用

缺點(diǎn):1.需要編譯器的配合静陈,編譯器要生成特殊的指令來進(jìn)行引用計(jì)數(shù)的操作;2.不能處理循環(huán)引用的問題

因此這種方法是垃圾收集的早期策略诞丽,現(xiàn)在很少使用鲸拥。Sun****的JVM****并沒有采用引用計(jì)數(shù)算法來進(jìn)行垃圾回收拐格,是基于根搜索算法的

2.2 根搜索算法

通過一系列的名為“GC Root”的對(duì)象作為起點(diǎn)刑赶,從這些節(jié)點(diǎn)向下搜索禁荒,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Root沒有任何引用鏈相連時(shí)角撞,則該對(duì)象不可達(dá),該對(duì)象是不可使用的勃痴,垃圾收集器將回收其所占的內(nèi)存谒所。

在java語言中,可作為GC Root的對(duì)象包括以下幾種對(duì)象:

  • java虛擬機(jī)棧(棧幀中的本地變量表)中的引用的對(duì)象沛申。
  • 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象劣领。
  • 方法區(qū)中的常量引用的對(duì)象。
  • 本地方法棧中JNI本地方法的引用對(duì)象铁材。

判斷無用的類:

  • 該類的所有實(shí)例都已經(jīng)被回收尖淘,即java堆中不存在該類的實(shí)例對(duì)象。
  • 加載該類的類加載器已經(jīng)被回收著觉。
  • 該類所對(duì)應(yīng)的java.lang.Class對(duì)象沒有任何地方被引用村生,無法在任何地方通過反射機(jī)制訪問該類的方法。

2.3 四種引用

GC在收集一個(gè)對(duì)象的時(shí)候會(huì)判斷是否有引用指向?qū)ο蟊穑贘AVA中的引用主要有四種:

  • 強(qiáng)引用(Strong Reference)

強(qiáng)引用是使用最普遍的引用趁桃。如果一個(gè)對(duì)象具有強(qiáng)引用,那垃圾回收器絕不會(huì)回收它肄鸽。當(dāng)內(nèi)存空間不足卫病,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤,使程序異常終止典徘,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來解決內(nèi)存不足的問題蟀苛。

  • 軟引用(Soft Reference)

如果一個(gè)對(duì)象只具有軟引用,則內(nèi)存空間足夠逮诲,垃圾回收器就不會(huì)回收它帜平;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存汛骂。只要垃圾回收器沒有回收它罕模,該對(duì)象就可以被程序使用。軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存帘瞭。

下面舉個(gè)例子淑掌,假如有一個(gè)應(yīng)用需要讀取大量的本地圖片,如果每次讀取圖片都從硬盤讀取蝶念,則會(huì)嚴(yán)重影響性能抛腕,但是如果全部加載到內(nèi)存當(dāng)中芋绸,又有可能造成內(nèi)存溢出,此時(shí)使用軟引用可以解決這個(gè)問題担敌。

設(shè)計(jì)思路是:用一個(gè)HashMap來保存圖片的路徑和相應(yīng)圖片對(duì)象關(guān)聯(lián)的軟引用之間的映射關(guān)系摔敛,在內(nèi)存不足時(shí),JVM會(huì)自動(dòng)回收這些緩存圖片對(duì)象所占用的空間全封,從而有效地避免了內(nèi)存溢出的問題马昙。

軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收器回收刹悴,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中行楞。

  • 弱引用(Weak Reference)

弱引用與軟引用的區(qū)別在于:只具有弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中土匀,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象子房,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存就轧。不過证杭,由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象妒御。

弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用解愤,如果弱引用所引用的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中携丁。

  • 虛引用(Phantom Reference)

“虛引用”顧名思義琢歇,就是形同虛設(shè),與其他幾種引用都不同梦鉴,虛引用并不會(huì)決定對(duì)象的生命周期李茫。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒有任何引用一樣肥橙,在任何時(shí)候都可能被垃圾回收器回收魄宏。

虛引用主要用于檢測(cè)對(duì)象是否已經(jīng)從內(nèi)存中刪除,跟蹤對(duì)象被垃圾回收器回收的活動(dòng)存筏。虛引用與軟引用和弱引用的一個(gè)區(qū)別在于:虛引用必須和引用隊(duì)列 (ReferenceQueue****)聯(lián)合使用宠互。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用椭坚,就會(huì)在回收對(duì)象的內(nèi)存之前予跌,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);

3善茎、策略:JVM中的垃圾收集策略

3.1 標(biāo)記-清除算法

1.png

標(biāo)記清除收集器停止所有的工作券册,從根掃描每個(gè)活躍的對(duì)象,然后標(biāo)記掃描過的對(duì)象,標(biāo)記完成以后烁焙,清除那些沒有被標(biāo)記的對(duì)象航邢。

優(yōu)點(diǎn):

  • 解決循環(huán)引用的問題
  • 不需要編譯器的配合,從而就不執(zhí)行額外的指令

缺點(diǎn):

  • 每個(gè)活躍的對(duì)象都要進(jìn)行掃描骄蝇,收集暫停的時(shí)間比較長(zhǎng)膳殷。
  • 標(biāo)記-清除算法采用從根集合進(jìn)行掃描,對(duì)存活的對(duì)象對(duì)象標(biāo)記九火,標(biāo)記完畢后赚窃,再掃描整個(gè)空間中未被標(biāo)記的對(duì)象,進(jìn)行回收岔激,如上圖所示考榨。

標(biāo)記-清除算法不需要進(jìn)行對(duì)象的移動(dòng),并且僅對(duì)不存活的對(duì)象進(jìn)行處理鹦倚,在存活對(duì)象比較多的情況下極為高效,但由于標(biāo)記-清除算法直接回收不存活的對(duì)象冀惭,因此會(huì)造成內(nèi)存碎片震叙。

3.2 復(fù)制算法

2.png

復(fù)制收集器將內(nèi)存分為兩塊一樣大小空間,某一個(gè)時(shí)刻散休,只有一個(gè)空間處于活躍的狀態(tài)媒楼,當(dāng)活躍的空間滿的時(shí)候,GC就會(huì)將活躍的對(duì)象復(fù)制到未使用的空間中去戚丸,原來不活躍的空間就變?yōu)榱嘶钴S的空間划址。

優(yōu)點(diǎn):

1 只掃描可以到達(dá)的對(duì)象,不需要掃描所有的對(duì)象限府,從而減少了應(yīng)用暫停的時(shí)間

缺點(diǎn):

1.需要額外的空間消耗夺颤,某一個(gè)時(shí)刻,總是有一塊內(nèi)存處于未使用狀態(tài)

2.復(fù)制對(duì)象需要一定的開銷

復(fù)制算法采用從根集合掃描胁勺,并將存活對(duì)象復(fù)制到一塊新的世澜,沒有使用過的空間中,這種算法當(dāng)空間存活的對(duì)象比較少時(shí)署穗,極為高效寥裂,但是帶來的成本是需要一塊內(nèi)存交換空間用于進(jìn)行對(duì)象的移動(dòng)。

3.3 標(biāo)記-整理算法

3.png

標(biāo)記整理收集器汲取了標(biāo)記清除和復(fù)制收集器的優(yōu)點(diǎn)案疲,它分兩個(gè)階段執(zhí)行封恰,在第一個(gè)階段,首先掃描所有活躍的對(duì)象褐啡,并標(biāo)記所有活躍的對(duì)象诺舔,第二個(gè)階段首先清除未標(biāo)記的對(duì)象,然后將活躍的的對(duì)象復(fù)制到堆得底部

該算法極大的減少了內(nèi)存碎片,并且不需要像復(fù)制算法一樣需要兩倍的空間混萝。

標(biāo)記-整理算法采用標(biāo)記-清除算法一樣的方式進(jìn)行對(duì)象的標(biāo)記遗遵,但在清除時(shí)不同,在回收不存活的對(duì)象占用的空間后逸嘀,會(huì)將所有的存活對(duì)象往左端空閑空間移動(dòng)车要,并更新對(duì)應(yīng)的指針。標(biāo)記-整理算法是在標(biāo)記-清除算法的基礎(chǔ)上崭倘,又進(jìn)行了對(duì)象的移動(dòng)翼岁,因此成本更高,但是卻解決了內(nèi)存碎片的問題司光。

3.4 分代回收

垃圾分代回收(Generational Collecting) 基于對(duì)對(duì)象生命周期分析后得出的垃圾回收算法琅坡。

因?yàn)槲覀兦懊嬗薪榻B,內(nèi)存主要被分為三塊残家,新生代榆俺、舊生代、持久代坞淮。三代的特點(diǎn)不同茴晋,造就了他們所用的GC算法不同,新生代適合那些生命周期較短回窘,頻繁創(chuàng)建及銷毀的對(duì)象诺擅,舊生代適合生命周期相對(duì)較長(zhǎng)的對(duì)象,持久代在Sun HotSpot中就是指方法區(qū)(有些JVM中根本就沒有持久代這中說法)啡直。首先介紹下新生代烁涌、舊生代、持久代的概念及特點(diǎn)酒觅。

4.png

Young(年輕代撮执、新生代):JVM specification中的 Heap的一部份 年輕代分三個(gè)區(qū)。一個(gè)Eden區(qū)舷丹,兩個(gè)Survivor區(qū)二打。大部分對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí)掂榔,還存活的對(duì)象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè))继效,當(dāng)這個(gè)Survivor區(qū)滿時(shí),此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū)装获,當(dāng)這個(gè)Survivor去也滿了的時(shí)候瑞信,從第一個(gè)Survivor區(qū)復(fù)制過來的并且此時(shí)還存活的對(duì)象,將被復(fù)制舊生代穴豫。需要注意凡简,Survivor的兩個(gè)區(qū)是對(duì)稱的逼友,沒先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí)存在從Eden復(fù)制過來對(duì)象秤涩,和從前一個(gè)Survivor復(fù)制過來的對(duì)象帜乞,而復(fù)制到年老區(qū)的只有從第一個(gè)Survivor去過來的對(duì)象。而且筐眷,Survivor區(qū)總有一個(gè)是空的黎烈。

新生代使用復(fù)制算法和標(biāo)記-清除垃圾收集算法,新生代中98%的對(duì)象是朝生夕死的短生命周期對(duì)象匀谣,所以不需要將新生代劃分為容量大小相等的兩部分內(nèi)存照棋,而是將新生代分為Eden區(qū),Survivor from(Survivor 0)和Survivor to(Survivor 1)三部分武翎,其占新生代內(nèi)存容量默認(rèn)比例分別為8:1:1烈炭,其中Survivor from和Survivor to總有一個(gè)區(qū)域是空白,只有Eden和其中一個(gè)Survivor總共90%的新生代容量用于為新創(chuàng)建的對(duì)象分配內(nèi)存宝恶,只有10%的Survivor內(nèi)存浪費(fèi)符隙,當(dāng)新生代內(nèi)存空間不足需要進(jìn)行垃圾回收時(shí),仍然存活的對(duì)象被復(fù)制到空白的Survivor內(nèi)存區(qū)域中垫毙,Eden和非空白的Survivor進(jìn)行標(biāo)記-清理回收膏执,兩個(gè)Survivor區(qū)域是輪換的。

如果空白Survivor空間無法存放下仍然存活的對(duì)象時(shí)露久,使用內(nèi)存分配擔(dān)保機(jī)制,直接將新生代依然存活的對(duì)象復(fù)制到年老代內(nèi)存中欺栗,同時(shí)對(duì)于創(chuàng)建大對(duì)象時(shí)毫痕,如果新生代中無足夠的連續(xù)內(nèi)存時(shí),也直接在年老代中分配內(nèi)存空間迟几。

Java虛擬機(jī)對(duì)新生代的垃圾回收稱為Minor GC消请,次數(shù)比較頻繁,每次回收時(shí)間也比較短类腮。

使用java虛擬機(jī)-Xmn參數(shù)可以指定新生代內(nèi)存大小臊泰。

Tenured(年老代、舊生代):JVM specification中的 Heap的一部份 年老代存放從年輕代存活的對(duì)象蚜枢。一般來說年老代存放的都是生命期較長(zhǎng)的對(duì)象缸逃。

年老代中的對(duì)象一般都是長(zhǎng)生命周期對(duì)象,對(duì)象的存活率比較高厂抽,因此在年老代中使用標(biāo)記-整理垃圾回收算法需频。

Java虛擬機(jī)對(duì)年老代的垃圾回收稱為MajorGC/Full GC,次數(shù)相對(duì)比較少筷凤,每次回收的時(shí)間也比較長(zhǎng)昭殉。

java虛擬機(jī)-Xms參數(shù)可以指定最小內(nèi)存大小苞七,-Xmx參數(shù)可以指定最大內(nèi)存大小,這兩個(gè)參數(shù)分別減去Xmn參數(shù)指定的新生代內(nèi)存大小挪丢,可以計(jì)算出年老代最小和最大內(nèi)存容量蹂风。

Perm(持久代、永久代): JVM specification中的 Method area 用于存放靜態(tài)文件乾蓬,如今Java類惠啄、方法等。持久代對(duì)垃圾回收沒有顯著影響巢块,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class礁阁,例如Hibernate等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來存放這些運(yùn)行過程中新增的類族奢。

java虛擬機(jī)內(nèi)存中的方法區(qū)在Sun HotSpot虛擬機(jī)中被稱為永久代姥闭,是被各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息越走、常量棚品、靜態(tài)變量、即時(shí)編譯后的代碼等數(shù)據(jù)廊敌。永久代垃圾回收比較少铜跑,效率也比較低,但是也必須進(jìn)行垃圾回收骡澈,否則會(huì)永久代內(nèi)存不夠用時(shí)仍然會(huì)拋出OutOfMemoryError異常锅纺。

永久代也使用標(biāo)記-整理算法進(jìn)行垃圾回收,java虛擬機(jī)參數(shù)-XX:PermSize-XX:MaxPermSize可以設(shè)置永久代的初始大小和最大容量肋殴。

3.5 垃圾回收過程

上面我們看了JVM的內(nèi)存分區(qū)管理囤锉,現(xiàn)在我們來看JVM的垃圾回收工作是怎樣運(yùn)作的。

首先當(dāng)啟動(dòng)J2EE應(yīng)用服務(wù)器時(shí)护锤,JVM隨之啟動(dòng)官地,并將JDK的類和接口,應(yīng)用服務(wù)器運(yùn)行時(shí)需要的類和接口以及J2EE應(yīng)用的類和接口定義文件也及編譯后的Class文件或JAR包中的Class文件裝載到JVM的永久存儲(chǔ)區(qū)烙懦。在伊甸園中創(chuàng)建JVM驱入,應(yīng)用服務(wù)器運(yùn)行時(shí)必須的JAVA對(duì)象,創(chuàng)建J2EE應(yīng)用啟動(dòng)時(shí)必須創(chuàng)建的JAVA對(duì)象氯析;J2EE應(yīng)用啟動(dòng)完畢亏较,可對(duì)外提供服務(wù)。

JVM在伊甸園區(qū)根據(jù)用戶的每次請(qǐng)求創(chuàng)建相應(yīng)的JAVA對(duì)象掩缓,當(dāng)伊甸園的空間不足以用來創(chuàng)建新JAVA對(duì)象的時(shí)候宴杀,JVM的垃圾回收器執(zhí)行對(duì)伊甸園區(qū)的垃圾回收工作,銷毀那些不再被其他對(duì)象引用的JAVA對(duì)象(如果該對(duì)象僅僅被一個(gè)沒有其他對(duì)象引用的對(duì)象引用的話拾因,此對(duì)象也被歸為沒有存在的必要旺罢,依此類推)旷余,并將那些被其他對(duì)象所引用的JAVA對(duì)象移動(dòng)到幸存者0區(qū)。

如果幸存者0區(qū)有足夠空間存放則直接放到幸存者0區(qū)扁达;如果幸存者0區(qū)沒有足夠空間存放正卧,則JVM的垃圾回收器執(zhí)行對(duì)幸存者0區(qū)的垃圾回收工作,銷毀那些不再被其他對(duì)象引用的JAVA對(duì)象跪解,并將那些被其他對(duì)象所引用的JAVA對(duì)象移動(dòng)到幸存者1區(qū)炉旷。

如果幸存者1區(qū)有足夠空間存放則直接放到幸存者1區(qū);如果幸存者1區(qū)沒有足夠空間存放叉讥,則JVM的垃圾回收器執(zhí)行對(duì)幸存者1區(qū)的垃圾回收工作窘行,銷毀那些不再被其他對(duì)象引用的JAVA對(duì)象,并將那些被其他對(duì)象所引用的JAVA對(duì)象移動(dòng)到養(yǎng)老區(qū)图仓。

如果養(yǎng)老區(qū)有足夠空間存放則直接放到養(yǎng)老區(qū)罐盔;如果養(yǎng)老區(qū)沒有足夠空間存放,則JVM的垃圾回收器執(zhí)行對(duì)養(yǎng)老區(qū)區(qū)的垃圾回收工作救崔,銷毀那些不再被其他對(duì)象引用的JAVA對(duì)象惶看,并保留那些被其他對(duì)象所引用的JAVA對(duì)象。

如果到最后養(yǎng)老區(qū)六孵,幸存者1區(qū)纬黎,幸存者0區(qū)和伊甸園區(qū)都沒有空間的話,則JVM會(huì)報(bào)告“JVM堆空間溢出(java.lang.OutOfMemoryError: Java heap space)”劫窒,也即是在堆空間沒有空間來創(chuàng)建對(duì)象本今。

這就是JVM的內(nèi)存分區(qū)管理,相比不分區(qū)來說冠息;一般情況下,垃圾回收的速度要快很多;因?yàn)樵跊]有必要的時(shí)候不用掃描整片內(nèi)存而節(jié)省了大量時(shí)間檬果。

3.6 對(duì)象的空間分配和晉升

  • 對(duì)象優(yōu)先在Eden上分配
  • 大對(duì)象直接進(jìn)入老年代。虛擬機(jī)提供了-XX:PretenureSizeThreshold參數(shù)唐断,大于這個(gè)參數(shù)值的對(duì)象將直接分配到老年代中选脊。因?yàn)樾律捎玫氖菢?biāo)記-復(fù)制策略,在Eden中分配大對(duì)象將會(huì)導(dǎo)致Eden區(qū)和兩個(gè)Survivor區(qū)之間大量的內(nèi)存拷貝脸甘。
  • 長(zhǎng)期存活的對(duì)象將進(jìn)入老年代恳啥。對(duì)象在Survivor區(qū)中每熬過一次Minor GC,年齡就增加1歲丹诀,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲)時(shí)钝的,就會(huì)晉升到老年代中翁垂。

4、觸發(fā):何時(shí)開始GC

Minor GC(新生代回收)的觸發(fā)條件比較簡(jiǎn)單硝桩,Eden空間不足就開始進(jìn)行Minor GC回收新生代沿猜。

而Full GC(老年代回收,一般伴隨一次Minor GC)則有幾種觸發(fā)條件:

  • 老年代空間不足

  • PermSpace空間不足

  • 統(tǒng)計(jì)得到的Minor GC晉升到老年代的平均大小大于老年代的剩余空間

這里注意一點(diǎn):PermSpace并不等同于方法區(qū)碗脊,只不過是Hotspot JVM用PermSpace來實(shí)現(xiàn)方法區(qū)而已啼肩,有些虛擬機(jī)沒有PermSpace而用其他機(jī)制來實(shí)現(xiàn)方法區(qū)。

5衙伶、實(shí)現(xiàn):JVM中的回收器類型

5.1 收集器概覽

Oracle Hotspot JVM中實(shí)現(xiàn)了多種垃圾收集器祈坠,針對(duì)不同的年齡代內(nèi)存中的對(duì)象的生存周期和應(yīng)用程序的特點(diǎn),實(shí)現(xiàn)了多款垃圾收集器矢劲。

單線程GC收集器包括SerialSerialOld這兩款收集器赦拘,分別用于年輕代和老年代的垃圾收集工作。后來卧须,隨著CPU多核的普及另绩,為了更好了利用多核的優(yōu)勢(shì),開發(fā)了ParNew收集器花嘶,這款收集器是Serial收集器的多線程版本笋籽。

多線程收集器還包括Parallel ScavengeParallelOld收集器,這兩款也分別用于年輕代和老年代的垃圾收集工作椭员,不同的是车海,它們是兩款可以利用多核優(yōu)勢(shì)的多線程收集器。

相對(duì)來說更加復(fù)雜的還有CMS收集器隘击。這款收集器侍芝,在運(yùn)行的時(shí)候會(huì)分多個(gè)階段進(jìn)行垃圾收集,而且在一些階段是可以和應(yīng)用線程并行運(yùn)行的埋同,提高了這款收集器的收集效率州叠。

其中最先進(jìn)的收集器,要數(shù)G1這款收集器了凶赁。這款收集器是當(dāng)前最新發(fā)布的收集器咧栗,是一款面向服務(wù)端垃圾收集器。

接下來虱肄,我們來分別介紹下上面提到的那些GC收集器以及它們各自的特點(diǎn)致板。

5.2 年輕代收集器

年輕代收集器包括Serial收集器、ParNew收集器以及Parallel Scavenge收集器咏窿。

5.2.1 Serial收集器

Serial收集器是一款年輕代的垃圾收集器斟或,使用標(biāo)記-復(fù)制垃圾收集算法。它是一款發(fā)展歷史最悠久的垃圾收集器集嵌。Serial收集器只能使用一條線程進(jìn)行垃圾收集工作萝挤,并且在進(jìn)行垃圾收集的時(shí)候御毅,所有的工作線程都需要停止工作,等待垃圾收集線程完成以后平斩,其他線程才可以繼續(xù)工作亚享。工作過程可以簡(jiǎn)單的用下圖來表示:

5.png

從圖中可以看到,Serial收集器工作的時(shí)候绘面,其他用戶線程都停止下來欺税,等到GC過程結(jié)束以后,它們才繼續(xù)執(zhí)行揭璃。而且處理GC過程的只有一條線程在執(zhí)行晚凿。由于Serial收集器的這種工作機(jī)制,所以在進(jìn)行垃圾收集過程中瘦馍,會(huì)出現(xiàn)STWStop The World)的情況歼秽,應(yīng)用程序會(huì)出現(xiàn)停頓的狀況。如果垃圾收集的時(shí)間很長(zhǎng)情组,那么停頓時(shí)間也會(huì)很長(zhǎng)燥筷,這樣會(huì)導(dǎo)致系統(tǒng)響應(yīng)變的遲鈍,影響系統(tǒng)的時(shí)候院崇。

雖然這款年邁的垃圾收集器只能使用單核CPU肆氓,但是正是由于它不能利用多核,在一些場(chǎng)景下底瓣,減少了很多線程的上下文切換的開銷谢揪,可以在進(jìn)行垃圾收集過程中專心處理GC過程,而不會(huì)被打斷捐凭,所以如果GC過程很短暫拨扶,那么這款收集器還是非常簡(jiǎn)單高效的。

由于Serial收集器只能使用單核CPU茁肠,在現(xiàn)代處理器基本都是多核多線程的情況下患民,為了充分利用多核的優(yōu)勢(shì),出現(xiàn)了多線程版本的垃圾收集器垦梆,比如下面將要說到的ParNew收集器匹颤。

5.2.2 ParNew收集器

ParNew垃圾收集器是Serial收集器的多線程版本,使用標(biāo)記-復(fù)制垃圾收集算法奶赔。為了利用CPU多核多線程的優(yōu)勢(shì)惋嚎,ParNew收集器可以運(yùn)行多個(gè)收集線程來進(jìn)行垃圾收集工作杠氢。這樣可以提高垃圾收集過程的效率站刑。

6.png

和上面的Serial收集器比較,可以明顯看到鼻百,在垃圾收集過程中绞旅,GC線程是多線程執(zhí)行的摆尝,而在Serial收集器中,只有一個(gè)GC線程在處理垃圾收集過程因悲。ParNew收集器在很多時(shí)候都是作為服務(wù)端的年輕代收集器的選擇堕汞,除了它具有比Serial收集器更好的性能外,還有一個(gè)原因是晃琳,多線程版本的年輕代收集器中讯检,只有它可以和CMS這款優(yōu)秀的老年代收集器一起搭配搭配使用。

作為一款多線程收集器卫旱,當(dāng)它運(yùn)行在單CPU的機(jī)器上的時(shí)候人灼,由于不能利用多核的優(yōu)勢(shì),在線程收集過程中可能會(huì)出現(xiàn)頻繁上下文切換顾翼,導(dǎo)致額外的開銷投放,所以在單CPU的機(jī)器上,ParNew收集器的性能不一定好于Serial這款單線程收集器适贸。如果機(jī)器是多CPU的灸芳,那么ParNew還是可以很好的提高GC收集的效率的。

ParNew收集器默認(rèn)開啟的垃圾收集線程數(shù)是和當(dāng)前機(jī)器的CPU數(shù)量相同的拜姿,為了控制GC收集線程的數(shù)量烙样,可以通過參數(shù)-XX:ParallelGCThreads來控制垃圾收集線程的數(shù)量。

5.2.3 Parallel Scavenge收集器

Parallel Scavenge收集器是是一款年輕代的收集器砾隅,它使用標(biāo)記-復(fù)制垃圾收集算法误阻。和ParNew一樣,它也會(huì)一款多線程的垃圾收集器晴埂,但是它又和ParNew有很大的不同點(diǎn)究反。

Parallel Scavenge收集器和其他收集器的關(guān)注點(diǎn)不同。其他收集器儒洛,比如ParNew和CMS這些收集器精耐,它們主要關(guān)注的是如何縮短垃圾收集的時(shí)間。而Parallel Scavenge收集器關(guān)注的是如何控制系統(tǒng)運(yùn)行的吞吐量琅锻。這里說的吞吐量卦停,指的是CPU用于運(yùn)行應(yīng)用程序的時(shí)間和CPU總時(shí)間的占比:

吞吐量 = 代碼運(yùn)行時(shí)間 / (代碼運(yùn)行時(shí)間 +垃圾收集時(shí)間)

如果虛擬機(jī)運(yùn)行的總的CPU時(shí)間是100分鐘,而用于執(zhí)行垃圾收集的時(shí)間為1分鐘恼蓬,那么吞吐量就是99%惊完。

7.png

直觀上,好像以縮短垃圾收集的停頓時(shí)間為目的和以控制吞吐量為目的差不多处硬,但是適用的場(chǎng)景卻不同小槐。對(duì)于那些桌面應(yīng)用程序油猫,為了得到良好的用戶體驗(yàn)模她,在交互過程中,需要得到快速的響應(yīng),所以系統(tǒng)的停頓時(shí)間要盡可能的快以避免影響到系統(tǒng)的響應(yīng)速度丐箩,只要保證每次停頓的時(shí)間很短暫皱炉,假設(shè)每次停頓時(shí)間為10ms庸推,那么即使發(fā)生很多次的垃圾收集過程蒜茴,假設(shè)1000次,也不會(huì)影響到系統(tǒng)的響應(yīng)速度疆栏,不會(huì)影響到用戶的體驗(yàn)曾掂。對(duì)于一些后臺(tái)計(jì)算任務(wù),它不需要和用戶進(jìn)行交互壁顶,所以短暫的停頓時(shí)間對(duì)它而言并不需要遭殉,對(duì)于計(jì)算任務(wù)而言,更好的利用CPU時(shí)間博助,提高計(jì)算效率才是需要的险污,所以假設(shè)每次停頓時(shí)間相對(duì)很長(zhǎng),有100ms富岳,而由于花費(fèi)了很長(zhǎng)的時(shí)間進(jìn)行垃圾收集蛔糯,那么垃圾收集的次數(shù)就會(huì)降下來,假設(shè)只有5次窖式,那么顯然蚁飒,使用以吞吐量為目的的垃圾收集器,可以更加有效的利用CPU來完成計(jì)算任務(wù)萝喘。所以淮逻,在用戶界面程序中,使用低延遲的垃圾收集器會(huì)有很好的效果阁簸,而對(duì)于后臺(tái)計(jì)算任務(wù)的系統(tǒng)爬早,高吞吐量的收集器才是首選。

Parallel Scavenge收集器提供了兩個(gè)參數(shù)用于控制吞吐量启妹。-XX:MaxGCPauseMillis用于控制最大垃圾收集停頓時(shí)間筛严,-XX:GCTimeRatio用于直接控制吞吐量的大小。MaxGCPauseMillis參數(shù)的值允許是一個(gè)大于0的整數(shù)饶米,表示毫秒數(shù)桨啃,收集器會(huì)盡可能的保證每次垃圾收集耗費(fèi)的時(shí)間不超過這個(gè)設(shè)定值。但是如果這個(gè)這個(gè)值設(shè)定的過小檬输,那么Parallel Scavenge收集器為了保證每次垃圾收集的時(shí)間不超過這個(gè)限定值照瘾,會(huì)導(dǎo)致垃圾收集的次數(shù)增加和增加年輕代的空間大小,垃圾收集的吞吐量也會(huì)隨之下降丧慈。GCTimeRatio這個(gè)參數(shù)的值應(yīng)該是一個(gè)0-100之間的整數(shù)析命,表示應(yīng)用程序運(yùn)行時(shí)間和垃圾收集時(shí)間的比值。如果把值設(shè)置為19,即系統(tǒng)運(yùn)行時(shí)間 : GC收集時(shí)間 = 19 : 1碳却,那么GC收集時(shí)間就占用了總時(shí)間的5%(1 / (19 + 1) = 5%),該參數(shù)的默認(rèn)值為99笑旺,即最大允許1%(1 / (1 + 99) = 1%)的垃圾收集時(shí)間昼浦。

Parallel Scavenge收集器還有一個(gè)參數(shù):-XX:UseAdaptiveSizePolicy。這是一個(gè)開關(guān)參數(shù)筒主,當(dāng)開啟這個(gè)參數(shù)以后关噪,就不需要手動(dòng)指定新生代的內(nèi)存大小(-Xmn)、Eden區(qū)和Survivor區(qū)的比值(-XX:SurvivorRatio)以及晉升到老年代的對(duì)象的大小(-XX:PretenureSizeThreshold)等參數(shù)了乌妙,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況動(dòng)態(tài)調(diào)整合適的設(shè)置值來達(dá)到合適的停頓時(shí)間和合適的吞吐量使兔,這種方式稱為GC自適應(yīng)調(diào)節(jié)策略

Parallel Scavenge收集器也是一款多線程收集器藤韵,但是由于目的是為了控制系統(tǒng)的吞吐量虐沥,所以這款收集器也被稱為吞吐量?jī)?yōu)先收集器。

5.3 老年代收集器

老年代收集包括:Serial Old收集器泽艘、Parallel Old收集器以及CMS收集器欲险。

5.3.1 Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,它也是一款使用標(biāo)記-整理算法的單線程的垃圾收集器匹涮。這款收集器主要用于客戶端應(yīng)用程序中作為老年代的垃圾收集器天试,也可以作為服務(wù)端應(yīng)用程序的垃圾收集器,當(dāng)它用于服務(wù)端應(yīng)用系統(tǒng)中的時(shí)候然低,主要是在JDK1.5版本之前和Parallel Scavenge年輕代收集器配合使用喜每,或者作為CMS收集器的后備收集器。

8.png

5.3.2 Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本雳攘,使用標(biāo)記-整理算法带兜。這個(gè)收集器是在JDK1.6版本中出現(xiàn)的,所以在JDK1.6之前吨灭,新生代的Parallel Scavenge只能和Serial Old這款單線程的老年代收集器配合使用鞋真。Parallel Old垃圾收集器和Parallel Scavenge收集器一樣,也是一款關(guān)注吞吐量的垃圾收集器沃于,和Parallel Scavenge收集器一起配合涩咖,可以實(shí)現(xiàn)對(duì)Java堆內(nèi)存的吞吐量?jī)?yōu)先的垃圾收集策略。

Parallel Old垃圾收集器的工作原理和Parallel Scavenge收集器類似繁莹。

9.png

5.3.3 CMS收集器

CMS收集器是目前老年代收集器中比較優(yōu)秀的垃圾收集器檩互。CMS是Concurrent Mark Sweep,從名字可以看出咨演,這是一款使用標(biāo)記-清除算法的并發(fā)收集器闸昨。CMS

垃圾收集器是一款以獲取最短停頓時(shí)間為目標(biāo)的收集器。由于現(xiàn)代互聯(lián)網(wǎng)中的應(yīng)用,比較重視服務(wù)的響應(yīng)速度和系統(tǒng)的停頓時(shí)間饵较,所以CMS收集器非常適合在這種場(chǎng)景下使用拍嵌。

CMS收集器的運(yùn)行過程相對(duì)上面提到的幾款收集器要復(fù)雜一些。

10.png

從圖中可以看出循诉,CMS收集器的工作過程可以分為4個(gè)階段:

  • 初始標(biāo)記(CMS initial mark)階段

  • 并發(fā)標(biāo)記(CMS concurrent mark)階段

  • 重新標(biāo)記(CMS remark)階段

  • 并發(fā)清除(CMS concurrent sweep)階段

從圖中可以看出横辆,在這4個(gè)階段中,初始標(biāo)記和重新標(biāo)記這兩個(gè)階段都是只有GC線程在運(yùn)行茄猫,用戶線程會(huì)被停止狈蚤,所以這兩個(gè)階段會(huì)發(fā)生STW(Stop The World)。初始標(biāo)記階段的工作是標(biāo)記GC Roots可以直接關(guān)聯(lián)到的對(duì)象划纽,速度很快脆侮。并發(fā)標(biāo)記階段,會(huì)從GC Roots 出發(fā)勇劣,標(biāo)記處所有可達(dá)的對(duì)象靖避,這個(gè)過程可能會(huì)花費(fèi)相對(duì)比較長(zhǎng)的時(shí)間,但是由于在這個(gè)階段比默,GC線程和用戶線程是可以一起運(yùn)行的筋蓖,所以即使標(biāo)記過程比較耗時(shí),也不會(huì)影響到系統(tǒng)的運(yùn)行退敦。重新標(biāo)記階段粘咖,是對(duì)并發(fā)標(biāo)記期間因用戶程序運(yùn)行而導(dǎo)致標(biāo)記變動(dòng)的那部分記錄進(jìn)行修正,重新標(biāo)記階段耗時(shí)一般比初始標(biāo)記稍長(zhǎng)侈百,但是遠(yuǎn)小于并發(fā)標(biāo)記階段瓮下。最終,會(huì)進(jìn)行并發(fā)清理階段钝域,和并發(fā)標(biāo)記階段類似讽坏,并發(fā)清理階段不會(huì)停止系統(tǒng)的運(yùn)行,所以即使相對(duì)耗時(shí)例证,也不會(huì)對(duì)系統(tǒng)運(yùn)行產(chǎn)生大的影響路呜。

由于并發(fā)標(biāo)記和并發(fā)清理階段是和應(yīng)用系統(tǒng)一起執(zhí)行的,而初始標(biāo)記和重新標(biāo)記相對(duì)來說耗時(shí)很短织咧,所以可以認(rèn)為CMS收集器在運(yùn)行過程中胀葱,是和應(yīng)用程序是并發(fā)執(zhí)行的。由于CMS收集器是一款并發(fā)收集和低停頓的垃圾收集器笙蒙,所以CMS收集器也被稱為并發(fā)低停頓收集器抵屿。

雖然CMS收集器可以是實(shí)現(xiàn)低延遲并發(fā)收集,但是也存在一些不足捅位。

首先轧葛,CMS收集器對(duì)CPU資源非常敏感搂抒。對(duì)于并發(fā)實(shí)現(xiàn)的收集器而言,雖然可以利用多核優(yōu)勢(shì)提高垃圾收集的效率尿扯,但是由于收集器在運(yùn)行過程中會(huì)占用一部分的線程求晶,這些線程會(huì)占用CPU資源,所以會(huì)影響到應(yīng)用系統(tǒng)的運(yùn)行衷笋,會(huì)導(dǎo)致系統(tǒng)總的吞吐量降低芳杏。CMS默認(rèn)開始的回收線程數(shù)是(Ncpu + 3) / 4,其中Ncpu是機(jī)器的CPU數(shù)右莱。所以,當(dāng)機(jī)器的CPU數(shù)量為4個(gè)以上的時(shí)候档插,垃圾回收線程將占用不少于%25的CPU資源慢蜓,并且隨著CPU數(shù)量的增加,垃圾回收線程占用的CPU資源會(huì)減少郭膛。但是晨抡,當(dāng)CPU資源少于4個(gè)的時(shí)候,垃圾回收線程占用的CPU資源的比例會(huì)增大则剃,會(huì)影響到系統(tǒng)的運(yùn)行耘柱,假設(shè)有2個(gè)CPU的情況下,垃圾回收線程將會(huì)占據(jù)超過50%的CPU資源棍现。所以调煎,在選用CMS收集器的時(shí)候,需要考慮己肮,當(dāng)前的應(yīng)用系統(tǒng)士袄,是否對(duì)CPU資源敏感。

其次谎僻,CMS收集器在處理垃圾收集的過程中娄柳,可能會(huì)產(chǎn)生浮動(dòng)垃圾,由于它無法處理浮動(dòng)垃圾艘绍,所以可能會(huì)出現(xiàn)Concurrent Mode Failure問題而導(dǎo)致觸發(fā)一次Full GC赤拒。所謂的浮動(dòng)垃圾,是由于CMS收集器的并發(fā)清理階段诱鞠,清理線程是和用戶線程一起運(yùn)行挎挖,如果在清理過程中,用戶線程產(chǎn)生了垃圾對(duì)象航夺,由于過了標(biāo)記階段肋乍,所以這些垃圾對(duì)象就成為了浮動(dòng)垃圾,CMS無法在當(dāng)前垃圾收集過程中集中處理這些垃圾對(duì)象敷存。由于這個(gè)原因墓造,CMS收集器不能像其他收集器那樣等到完全填滿了老年代以后才進(jìn)行垃圾收集堪伍,需要預(yù)留一部分空間來保證當(dāng)出現(xiàn)浮動(dòng)垃圾的時(shí)候可以有空間存放這些垃圾對(duì)象。在JDK 1.5中觅闽,默認(rèn)當(dāng)老年代使用了68%的時(shí)候會(huì)激活垃圾收集帝雇,這是一個(gè)保守的設(shè)置,如果在應(yīng)用中老年代增長(zhǎng)不是很快蛉拙,可以通過參數(shù)-XX:CMSInitiatingOccupancyFraction"控制觸發(fā)的百分比尸闸,以便降低內(nèi)存回收次數(shù)來提供性能。在JDK 1.6中孕锄,CMS收集器的激活閥值變成了92%吮廉。如果在CMS運(yùn)行期間沒有足夠的內(nèi)存來存放浮動(dòng)垃圾,那么就會(huì)導(dǎo)致"Concurrent Mode Failure"失敗畸肆,這個(gè)時(shí)候宦芦,虛擬機(jī)將啟動(dòng)后備預(yù)案,臨時(shí)啟動(dòng)Serial Old收集器來對(duì)老年代重新進(jìn)行垃圾收集轴脐,這樣會(huì)導(dǎo)致垃圾收集的時(shí)間邊長(zhǎng)调卑,特別是當(dāng)老年代內(nèi)存很大的時(shí)候。所以對(duì)參數(shù)"-XX:CMSInitiatingOccupancyFraction"的設(shè)置大咱,過高恬涧,會(huì)導(dǎo)致發(fā)生Concurrent Mode Failure,過低碴巾,則浪費(fèi)內(nèi)存空間溯捆。

CMS的最后一個(gè)問題,就是它在進(jìn)行垃圾收集時(shí)使用的標(biāo)記-清除算法厦瓢,會(huì)出現(xiàn)很多內(nèi)存碎片现使,過多的內(nèi)存碎片會(huì)影響大對(duì)象的分配,會(huì)導(dǎo)致即使老年代內(nèi)存還有很多空閑旷痕,但是由于過多的內(nèi)存碎片碳锈,不得不提前觸發(fā)垃圾回收。為了解決這個(gè)問題欺抗,CMS收集器提供了一個(gè)"-XX:+UseCMSCompactAtFullCollection"參數(shù)售碳,用于CMS收集器在必要的時(shí)候?qū)?nèi)存碎片進(jìn)行壓縮整理。由于內(nèi)存碎片整理過程不是并發(fā)的绞呈,所以會(huì)導(dǎo)致停頓時(shí)間變長(zhǎng)贸人。"-XX:+UseCMSCompactAtFullCollection"參數(shù)默認(rèn)是開啟的。虛擬機(jī)還提供了一個(gè)"-XX:CMSFullGCsBeforeCompaction"參數(shù)佃声,來控制進(jìn)行過多少次不壓縮的Full GC以后艺智,進(jìn)行一次帶壓縮的Full GC,默認(rèn)值是0圾亏,表示每次在進(jìn)行Full GC前都進(jìn)行碎片整理十拣。

雖然CMS收集器存在上面提到的這些問題封拧,但是毫無疑問,CMS當(dāng)前仍然是非常優(yōu)秀的垃圾收集器夭问。

5.4 G1收集器

G1(Garbage First)垃圾收集器是當(dāng)今垃圾回收技術(shù)最前沿的成果之一泽西。早在JDK7就已加入JVM的收集器大家庭中,成為HotSpot重點(diǎn)發(fā)展的垃圾回收技術(shù)缰趋。同優(yōu)秀的CMS垃圾回收器一樣捧杉,G1也是關(guān)注最小時(shí)延的垃圾回收器,也同樣適合大尺寸堆內(nèi)存的垃圾收集秘血,官方也推薦使用G1來代替選擇CMS味抖。G1最大的特點(diǎn)是引入分區(qū)的思路,弱化了分代的概念灰粮,合理利用垃圾收集各個(gè)周期的資源仔涩,解決了其他收集器甚至CMS的眾多缺陷。

G1收集與前面介紹的收集器有很大不同:

  • G1的設(shè)計(jì)原則是"首先收集盡可能多的垃圾(Garbage First)"谋竖。因此红柱,G1并不會(huì)等內(nèi)存耗盡(串行承匣、并行)或者快耗盡(CMS)的時(shí)候開始垃圾收集蓖乘,而是在內(nèi)部采用了啟發(fā)式算法,在老年代找出具有高收集收益的分區(qū)進(jìn)行收集韧骗。同時(shí)G1可以根據(jù)用戶設(shè)置的暫停時(shí)間目標(biāo)自動(dòng)調(diào)整年輕代和總堆大小嘉抒,暫停目標(biāo)越短年輕代空間越小、總空間就越大袍暴;

  • G1采用內(nèi)存分區(qū)(Region)的思路些侍,將內(nèi)存劃分為一個(gè)個(gè)相等大小的內(nèi)存分區(qū),回收時(shí)則以分區(qū)為單位進(jìn)行回收政模,存活的對(duì)象復(fù)制到另一個(gè)空閑分區(qū)中岗宣。由于都是以相等大小的分區(qū)為單位進(jìn)行操作,因此G1天然就是一種壓縮方案(局部壓縮)淋样;

  • G1雖然也是分代收集器耗式,但整個(gè)內(nèi)存分區(qū)不存在物理上的年輕代與老年代的區(qū)別,也不需要完全獨(dú)立的survivor(to space)堆做復(fù)制準(zhǔn)備趁猴。G1只有邏輯上的分代概念刊咳,或者說每個(gè)分區(qū)都可能隨G1的運(yùn)行在不同代之間前后切換;

  • G1的收集都是STW的儡司,但年輕代和老年代的收集界限比較模糊娱挨,采用了混合(mixed)收集的方式。即每次收集既可能只收集年輕代分區(qū)(年輕代收集)捕犬,也可能在收集年輕代的同時(shí)跷坝,包含部分老年代分區(qū)(混合收集)酵镜,這樣即使堆內(nèi)存很大時(shí),也可以限制收集范圍探孝,從而降低停頓笋婿。

Region區(qū)域劃分與其他收集類似,不同的是單獨(dú)將大對(duì)象分配到了單獨(dú)的region中顿颅,會(huì)分配一組連續(xù)的Region區(qū)域(Humongous start 和 humonous Contoinue 組成)缸濒,所以一共有四類Region(Eden,Survior粱腻,Humongous和Old):

11.png

G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型庇配,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大猩苄(回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值)捞慌,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間柬批,優(yōu)先回收價(jià)值最大的Region(這也就是Garbage-First名稱的來由)啸澡。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式,保證了G1收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率氮帐。

G1收集器的運(yùn)作大致可劃分為以下幾個(gè)步驟:

  • 初始標(biāo)記(Initial Marking)
    初始標(biāo)記階段僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象嗅虏,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)運(yùn)行時(shí)上沐,能在正確可用的Region中創(chuàng)建新對(duì)象皮服,這階段需要停頓線程,但耗時(shí)很短参咙。

  • 并發(fā)標(biāo)記(Concurrent Marking)
    并發(fā)標(biāo)記階段是從GC Root開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析龄广,找出存活的對(duì)象,這階段耗時(shí)較長(zhǎng)蕴侧,但可與用戶程序并發(fā)執(zhí)行择同。

  • 最終標(biāo)記(Final Marking)
    最終標(biāo)記階段是為了修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄,虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線程Remembered Set Logs里面净宵,最終標(biāo)記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中敲才,這階段需要停頓線程,但是可并行執(zhí)行塘娶。

  • 篩選回收(Live Data Counting and Evacuation)
    篩選回收階段首先對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序归斤,根據(jù)用戶所期望的GC停頓時(shí)間來制定回收計(jì)劃,這個(gè)階段其實(shí)也可以做到與用戶程序一起并發(fā)執(zhí)行刁岸,但是因?yàn)橹换厥找徊糠謨r(jià)值高的Region區(qū)的垃圾對(duì)象脏里,時(shí)間是用戶可控制的,而且停頓用戶線程將大幅提高收集效率虹曙∑群幔回收時(shí)番舆,采用“復(fù)制”算法,從一個(gè)或多個(gè)Region復(fù)制存活對(duì)象到堆上的另一個(gè)空的Region矾踱,并且在此過程中壓縮和釋放內(nèi)存恨狈。

G1部分設(shè)置參數(shù)如下:

  • "-XX:+UseG1GC":指定使用G1收集器;

  • "-XX:InitiatingHeapOccupancyPercent":當(dāng)整個(gè)Java堆的占用率達(dá)到參數(shù)值時(shí)呛讲,開始并發(fā)標(biāo)記階段禾怠;默認(rèn)為45;

  • "-XX:MaxGCPauseMillis":為G1設(shè)置暫停時(shí)間目標(biāo)贝搁,默認(rèn)值為200毫秒吗氏;

  • "-XX:G1HeapRegionSize":設(shè)置每個(gè)Region大小,范圍1MB到32MB雷逆;目標(biāo)是在最小Java堆時(shí)可以擁有約2048個(gè)

5.5 垃圾收集器總結(jié)

GC組合 Minor GC Full GC 描述
-XX:+UseSerialGC Serial收集器串行回收 Serial Old收集器串行回收 該選項(xiàng)可以手動(dòng)指定Serial收集器+Serial Old收集器組合執(zhí)行內(nèi)存回收
-XX:+UseParNewGC ParNew收集器并行回收 Serial Old收集器串行回收 該選項(xiàng)可以手動(dòng)指定ParNew收集器+Serilal Old組合執(zhí)行內(nèi)存回收
-XX:+UseParallelGC Parallel收集器并行回收 Serial Old收集器串行回收 該選項(xiàng)可以手動(dòng)指定Parallel收集器+Serial Old收集器組合執(zhí)行內(nèi)存回收
-XX:+UseParallelOldGC Parallel收集器并行回收 Parallel Old收集器并行回收 該選項(xiàng)可以手動(dòng)指定Parallel收集器+Parallel Old收集器組合執(zhí)行內(nèi)存回收
-XX:+UseConcMarkSweepGC ParNew收集器并行回收 缺省使用CMS收集器并發(fā)回收弦讽,備用采用Serial Old收集器串行回收 該選項(xiàng)可以手動(dòng)指定ParNew收集器+CMS收集器+Serial Old收集器組合執(zhí)行內(nèi)存回收。優(yōu)先使用ParNew收集器+CMS收集器的組合膀哲,當(dāng)出現(xiàn)ConcurrentMode Fail或者Promotion Failed時(shí)往产,則采用ParNew收集器+Serial Old收集器的組合
-XX:+UseConcMarkSweepGC -XX:-UseParNewGC Serial收集器串行回收
-XX:+UseG1GC G1收集器并發(fā)、并行執(zhí)行內(nèi)存回收 暫無

6某宪、GC日志分析

垃圾收集器在進(jìn)行垃圾收集的過程中仿村,可以輸出日志,我們通過日志缩抡,可以看到當(dāng)前垃圾收集器的運(yùn)行情況奠宜。通過gc日志包颁,我們可以觀察垃圾收集器的行為瞻想,以及當(dāng)前應(yīng)用程序的GC情況和內(nèi)存使用情況。學(xué)會(huì)查看和分析垃圾收集日志娩嚼,一方面可以幫助我們學(xué)習(xí)垃圾收集器蘑险;另一方面,在必要的時(shí)候岳悟,可以幫助我們定位問題佃迄,解決問題,對(duì)JVM進(jìn)行優(yōu)化贵少。

默認(rèn)呵俏,JVM不會(huì)打印出GC日志信息,可以通過參數(shù)-XX:+PrintGC-verbose:gc來設(shè)置JVM輸出gc日志到終端中滔灶。

JVM參數(shù):-XX:+PrintGC -XX:+UseSerialGC -Xms10m -Xmx10m

12.png

當(dāng)設(shè)置了"-XX:+PrintGC"或者"-verbose:gc"以后就會(huì)輸出類似輸出上面的GC日志普碎。這是最簡(jiǎn)單的GC日志,包含了垃圾收集過程中的信息录平。其中紅色部分的"GC"和"Full GC"表示這次GC的類型麻车,而綠色部分的"Allocation Failure"表示表示發(fā)生這次GC的原因缀皱,從上面的日志可以看出,是由于內(nèi)存分配失敗導(dǎo)致的GC动猬。后面的黃色部分"1922K->1394K(9920K)"表示這次GC導(dǎo)致JVM中堆內(nèi)存的使用量從1922K降低到了1394K啤斗,其中括號(hào)中表示當(dāng)前整個(gè)JVM堆的大小。最后藍(lán)色部分的"0.0021245 secs"表示這次GC持續(xù)的時(shí)間赁咙。

上面輸出的是簡(jiǎn)單格式的GC日志钮莲,雖然提供了一些信息,但是通過這些信息彼水,我們沒法知道這次GC發(fā)生的時(shí)候臂痕,這次GC是發(fā)生在老年代還是在年輕代,是否有對(duì)象從年輕代被移動(dòng)到了老年代等信息猿涨,所以我們希望可以看到更加詳盡的信息握童。這個(gè)時(shí)候,我們需要設(shè)置-XX:+PrintGCDetails參數(shù)來輸出更加詳細(xì)的GC日志叛赚,下面我們結(jié)合不同的收集器組合澡绩,來分析下它們的輸出日志。

6.1 Serial GC + Serial Old

Serial GC和Serial Old收集器是比較早的單線程收集器俺附,工作原理我們?cè)谏厦嬉呀?jīng)介紹過了肥卡。這里,我們來看下使用這兩款收集器進(jìn)行垃圾收集的時(shí)候事镣,輸出的日志格式是怎么樣的步鉴。首先我們需要設(shè)置JVM參數(shù):

JVM參數(shù):*-XX:+PrintGC -XX:+PrintGCDetails -XX:+UseSerialGC -Xms10m -Xmx10m*

13.png

可以發(fā)現(xiàn),通過設(shè)置了"-XX:+PrintGCDetails"以后璃哟,輸出的GC日志信息多了很多氛琢。我們先來看第一條,紅色部分"GC"表示這次發(fā)生的是Minor GC随闪,綠色部分"Allocation Failure"表示導(dǎo)致這次GC的原因是內(nèi)存分配失敗阳似。接下來,黃色部分的內(nèi)容铐伴,則和前面的日志有些區(qū)別了撮奏,這里輸出的內(nèi)容相對(duì)比較詳細(xì)。"DefNew: 1922K->319K(3072K), 0.0027356 secs] 1922K->1394K(9920K), 0.0027698 secs"当宴,其中DefNew表示這次GC發(fā)生在年輕代(不同的收集器畜吊,日志的格式不一定相同),接下來"1922K->319K"表示這次GC導(dǎo)致年輕代使用的內(nèi)存從1922K降到319K,括號(hào)中的"3072K"表示年輕代中的堆內(nèi)存大小為3072K户矢。"0.0027356 secs"表示這次年輕代GC耗時(shí)0.0027356s玲献。后面的"1922K->1393K"表示總的堆內(nèi)存(年輕代 + 老年代)的使用情況的變化,從1922K降低到1394K, 括號(hào)中的"9920K"表示總的堆內(nèi)存的大小。最后的"0.0027698 secs"表示這次GC總的消耗的時(shí)間青自。最后是這次GC消耗的時(shí)間的統(tǒng)計(jì)株依,其中user表示用戶態(tài)CPU執(zhí)行的時(shí)間,sys表示內(nèi)核態(tài)CPU執(zhí)行的時(shí)間延窜,這兩個(gè)時(shí)間不包括被掛起消耗的時(shí)間恋腕,而real表示的是實(shí)際的時(shí)間,可以認(rèn)為是墻上時(shí)鐘走過的時(shí)間逆瑞。

下面的這條日志荠藤,"Full GC"表示這次GC是一次Major GC,后面的原因和上面一樣获高。我們來看下黃色部分哈肖,"Tenured"表示這次GC發(fā)生在老年代,其中"6524K->6484K"表示老年代內(nèi)存從6524K降低到6484K念秧。后面的時(shí)間"0.0025899 secs"表示這次老年代GC耗時(shí)0.0025899s淤井。接下來的"8562K -> 8532K"和上面提到的一樣,表示整個(gè)堆內(nèi)存的變化摊趾。最后的時(shí)間表示這次GC的總耗時(shí)為"0.0026153s"币狠。

6.2 Parallel Scanvage + Parallel Old

不同的垃圾收集器,輸出的日志信息也不是完全相同的砾层,上面我們看到的日志漩绵,是使用Serial GC和Serial Old收集器輸出的gc日志,而下面的日志信息肛炮,則是使用Parallel Scavenge收集器和Parallel Old收集器輸出的日志止吐。

JVM參數(shù):-XX:+PrintGC -XX:+UseParallelOldGC -XX:+PrintGCDetails -Xms10m -Xmx10m

14.png

可以看到,使用Parallel Scavenge 和 Parallel Old收集器輸出的日志侨糟,會(huì)有一些不同碍扔,不過日志內(nèi)容大體上差不多。最后粟害,我們來看下CMS垃圾收集器的日志是怎么樣的蕴忆,相對(duì)上面幾款收集器颤芬,CMS相對(duì)更加復(fù)雜悲幅,從它輸出的日志也可以看出來。

6.3 ParNew + Concurrent Mark Sweep(CMS)

下面站蝠,我們來看下ParNew配合CMS收集器在進(jìn)行垃圾收集的時(shí)候汰具,輸出的GC 日志信息。

JVM參數(shù):-XX:+PrintGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xms10m -Xmx10m

15.png

通過第一條日志菱魔,可以看出我們使用"-XX:+UseConcMarkSweepGC"指定CMS垃圾收集器的時(shí)候留荔,使用的是ParNew + CMS收集器組合。下面輸出的一堆日志,就是CMS收集器在進(jìn)行垃圾收集過程中輸出的信息聚蝶〗芗耍可以明顯的看到,CMS在進(jìn)行垃圾收集的過程中碘勉,經(jīng)歷了4個(gè)階段巷挥,在日志中我用4中顏色標(biāo)記出來了。需要注意的是黃色部分验靡,這是CMS的重新標(biāo)記的階段倍宾,在上面我們介紹CMS收集器的時(shí)候說過,在這個(gè)階段胜嗓,是會(huì)出現(xiàn)Stop The World的高职,所以如果這個(gè)階段消耗的時(shí)間比較長(zhǎng),則會(huì)影響應(yīng)用的響應(yīng)時(shí)間辞州。

6.4 其他日志參數(shù)

有時(shí)候怔锌,我們需要在GC日志中輸出時(shí)間值,這樣我們就可以知道這次GC發(fā)生的具體時(shí)間點(diǎn)变过。我們可以通過JVM參數(shù)"-XX:+PrintGCDateStamps"來設(shè)置日志輸出的時(shí)間产禾。

16.png

除了將日志輸出到控制臺(tái),我們還可以將日志輸出到日志文件中牵啦,這樣就可以通過分析日志文件來分析系統(tǒng)的GCGCJVM"-Xloggc:"

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末亚情,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哈雏,更是在濱河造成了極大的恐慌楞件,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裳瘪,死亡現(xiàn)場(chǎng)離奇詭異土浸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)彭羹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門黄伊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人派殷,你說我怎么就攤上這事还最。” “怎么了毡惜?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵拓轻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我经伙,道長(zhǎng)扶叉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮枣氧,結(jié)果婚禮上溢十,老公的妹妹穿的比我還像新娘。我一直安慰自己达吞,他們只是感情好茶宵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宗挥,像睡著了一般乌庶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上契耿,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天瞒大,我揣著相機(jī)與錄音,去河邊找鬼搪桂。 笑死透敌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的踢械。 我是一名探鬼主播酗电,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼内列!你這毒婦竟也來了撵术?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤话瞧,失蹤者是張志新(化名)和其女友劉穎嫩与,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體交排,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡划滋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了埃篓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片处坪。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖架专,靈堂內(nèi)的尸體忽然破棺而出同窘,到底是詐尸還是另有隱情,我是刑警寧澤胶征,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布塞椎,位于F島的核電站,受9級(jí)特大地震影響睛低,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一钱雷、第九天 我趴在偏房一處隱蔽的房頂上張望骂铁。 院中可真熱鬧,春花似錦罩抗、人聲如沸拉庵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钞支。三九已至,卻和暖如春操刀,著一層夾襖步出監(jiān)牢的瞬間烁挟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工骨坑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撼嗓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓欢唾,卻偏偏與公主長(zhǎng)得像且警,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子礁遣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344