成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java基礎(chǔ)篇——JVM之GC原理(干貨滿滿)

liaorio / 2501人閱讀

摘要:此外,從結(jié)果我們可以得知,一個(gè)堆對(duì)象的放在局部變量表中的第一項(xiàng)引用會(huì)永遠(yuǎn)存在,在方法體內(nèi)可以將引用賦值給其他變量,這樣堆中對(duì)象就可以被其他變量所引用,即不會(huì)被回收。

原創(chuàng)不易,如需轉(zhuǎn)載,請(qǐng)注明出處https://www.cnblogs.com/baixianlong/p/10697554.html,多多支持哈!

一、什么是GC?

GC是垃圾收集的意思,內(nèi)存處理是編程人員容易出現(xiàn)問(wèn)題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動(dòng)監(jiān)測(cè)對(duì)象是否超過(guò)作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的,Java語(yǔ)言沒(méi)有提供釋放已分配內(nèi)存的顯示操作方法。Java程序員不用擔(dān)心內(nèi)存管理,因?yàn)槔占鲿?huì)自動(dòng)進(jìn)行管理。要請(qǐng)求垃圾收集,可以調(diào)用下面的方法之一:System.gc() 或Runtime.getRuntime().gc()。

二、哪些內(nèi)存需要回收?

哪些內(nèi)存需要回收是垃圾回收機(jī)制第一個(gè)要考慮的問(wèn)題,所謂“要回收的垃圾”無(wú)非就是那些不可能再被任何途徑使用的對(duì)象。那么如何找到這些對(duì)象?

引用計(jì)數(shù)法:這種算法不能解決對(duì)象之間相互引用的情況,所以這種方法不靠譜

可達(dá)性分析法:這個(gè)算法的基本思想是通過(guò)一系列稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)向下搜索,搜索所走過(guò)的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈(即GC Roots到對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的。

那么問(wèn)題又來(lái)了,如何選取GCRoots對(duì)象呢?在Java語(yǔ)言中,可以作為GCRoots的對(duì)象包括下面幾種:

虛擬機(jī)棧(棧幀中的局部變量區(qū),也叫做局部變量表)中引用的對(duì)象。

方法區(qū)中的類靜態(tài)屬性引用的對(duì)象。

方法區(qū)中常量引用的對(duì)象。

本地方法棧中JNI(Native方法)引用的對(duì)象。

下面給出一個(gè)GCRoots的例子,如下圖,為GCRoots的引用鏈,obj8、obj9、obj10都沒(méi)有到GCRoots對(duì)象的引用鏈,所以會(huì)進(jìn)行回收。

三、四種引用狀以及基于可達(dá)性分析的內(nèi)存回收原理

對(duì)于可達(dá)性分析算法而言,未到達(dá)的對(duì)象并非是“非死不可”的,若要宣判一個(gè)對(duì)象死亡,至少需要經(jīng)歷兩次標(biāo)記階段。

如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與GCRoots相連的引用鏈,則該對(duì)象被第一次標(biāo)記并進(jìn)行一次篩選,篩選條件為是否有必要執(zhí)行該對(duì)象的finalize方法,若對(duì)象沒(méi)有覆蓋finalize方法或者該finalize方法是否已經(jīng)被虛擬機(jī)執(zhí)行過(guò)了,則均視作不必要執(zhí)行該對(duì)象的finalize方法,即該對(duì)象將會(huì)被回收。反之,若對(duì)象覆蓋了finalize方法并且該finalize方法并沒(méi)有被執(zhí)行過(guò),那么,這個(gè)對(duì)象會(huì)被放置在一個(gè)叫F-Queue的隊(duì)列中,之后會(huì)由虛擬機(jī)自動(dòng)建立的、優(yōu)先級(jí)低的Finalizer線程去執(zhí)行,而虛擬機(jī)不必要等待該線程執(zhí)行結(jié)束,即虛擬機(jī)只負(fù)責(zé)建立線程,其他的事情交給此線程去處理。

對(duì)F-Queue中對(duì)象進(jìn)行第二次標(biāo)記,如果對(duì)象在finalize方法中拯救了自己,即關(guān)聯(lián)上了GCRoots引用鏈,如把this關(guān)鍵字賦值給其他變量,那么在第二次標(biāo)記的時(shí)候該對(duì)象將從“即將回收”的集合中移除,如果對(duì)象還是沒(méi)有拯救自己,那就會(huì)被回收。如下代碼演示了一個(gè)對(duì)象如何在finalize方法中拯救了自己,然而,它只能拯救自己一次,第二次就被回收了。具體代碼如下:

public class GC {

public static GC SAVE_HOOK = null; 

public static void main(String[] args) throws InterruptedException {
    // 新建對(duì)象,因?yàn)镾AVE_HOOK指向這個(gè)對(duì)象,對(duì)象此時(shí)的狀態(tài)是(reachable,unfinalized)
    SAVE_HOOK = new GC(); 
    //將SAVE_HOOK設(shè)置成null,此時(shí)剛才創(chuàng)建的對(duì)象就不可達(dá)了,因?yàn)闆](méi)有句柄再指向它了,對(duì)象此時(shí)狀態(tài)是(unreachable,unfinalized)
    SAVE_HOOK = null; 
    //強(qiáng)制系統(tǒng)執(zhí)行垃圾回收,系統(tǒng)發(fā)現(xiàn)剛才創(chuàng)建的對(duì)象處于unreachable狀態(tài),并檢測(cè)到這個(gè)對(duì)象的類覆蓋了finalize方法,因此把這個(gè)對(duì)象放入F-Queue隊(duì)列,由低優(yōu)先級(jí)線程執(zhí)行它的finalize方法,此時(shí)對(duì)象的狀態(tài)變成(unreachable, finalizable)或者是(finalizer-reachable,finalizable)
    System.gc(); 
    // sleep,目的是給低優(yōu)先級(jí)線程從F-Queue隊(duì)列取出對(duì)象并執(zhí)行其finalize方法提供機(jī)會(huì)。在執(zhí)行完對(duì)象的finalize方法中的super.finalize()時(shí),對(duì)象的狀態(tài)變成(unreachable,finalized)狀態(tài),但接下來(lái)在finalize方法中又執(zhí)行了SAVE_HOOK = this;這句話,又有句柄指向這個(gè)對(duì)象了,對(duì)象又可達(dá)了。因此對(duì)象的狀態(tài)又變成了(reachable, finalized)狀態(tài)。
    Thread.sleep(500);
    // 這里樓主說(shuō)對(duì)象處于(reachable,finalized)狀態(tài)應(yīng)該是合理的。對(duì)象的finalized方法被執(zhí)行了,因此是finalized狀態(tài)。又因?yàn)樵趂inalize方法是執(zhí)行了SAVE_HOOK=this這句話,本來(lái)是unreachable的對(duì)象,又變成reachable了。
    if (null != SAVE_HOOK) { //此時(shí)對(duì)象應(yīng)該處于(reachable, finalized)狀態(tài) 
        // 這句話會(huì)輸出,注意對(duì)象由unreachable,經(jīng)過(guò)finalize復(fù)活了。
        System.out.println("Yes , I am still alive"); 
    } else { 
        System.out.println("No , I am dead"); 
    } 
    // 再一次將SAVE_HOOK放空,此時(shí)剛才復(fù)活的對(duì)象,狀態(tài)變成(unreachable,finalized)
    SAVE_HOOK = null; 
    // 再一次強(qiáng)制系統(tǒng)回收垃圾,此時(shí)系統(tǒng)發(fā)現(xiàn)對(duì)象不可達(dá),雖然覆蓋了finalize方法,但已經(jīng)執(zhí)行過(guò)了,因此直接回收。
    System.gc(); 
    // 為系統(tǒng)回收垃圾提供機(jī)會(huì)
    Thread.sleep(500); 
    if (null != SAVE_HOOK) { 
        // 這句話不會(huì)輸出,因?yàn)閷?duì)象已經(jīng)徹底消失了。
        System.out.println("Yes , I am still alive"); 
    } else { 
        System.out.println("No , I am dead"); 
    } 
} 

@Override 
protected void finalize() throws Throwable { 
    super.finalize(); 
    System.out.println("execute method finalize()"); 
   // 這句話讓對(duì)象的狀態(tài)由unreachable變成reachable,就是對(duì)象復(fù)活
    SAVE_HOOK = this; 
} 

}

運(yùn)行結(jié)果如下:

    leesf
    null
    finalize method executed!
    leesf
    yes, i am still alive :)
    no, i am dead : (

  由結(jié)果可知,該對(duì)象拯救了自己一次,第二次沒(méi)有拯救成功,因?yàn)閷?duì)象的finalize方法最多被虛擬機(jī)調(diào)用一次。此外,從結(jié)果我們可以得知,一個(gè)堆對(duì)象的this(放在局部變量表中的第一項(xiàng))引用會(huì)永遠(yuǎn)存在,在方法體內(nèi)可以將this引用賦值給其他變量,這樣堆中對(duì)象就可以被其他變量所引用,即不會(huì)被回收。

四、方法區(qū)的垃圾回收

1、方法區(qū)的垃圾回收主要回收兩部分內(nèi)容:

廢棄常量

無(wú)用的類

2、既然進(jìn)行垃圾回收,就需要判斷哪些是廢棄常量,哪些是無(wú)用的類?

如何判斷廢棄常量呢?以字面量回收為例,如果一個(gè)字符串“abc”已經(jīng)進(jìn)入常量池,但是當(dāng)前系統(tǒng)沒(méi)有任何一個(gè)String對(duì)象引用了叫做“abc”的字面量,那么,如果發(fā)生垃圾回收并且有必要時(shí),“abc”就會(huì)被系統(tǒng)移出常量池。常量池中的其他類(接口)、方法、字段的符號(hào)引用也與此類似。

如何判斷無(wú)用的類呢?需要滿足以下三個(gè)條件:

該類的所有實(shí)例都已經(jīng)被回收,即Java堆中不存在該類的任何實(shí)例。

加載該類的ClassLoader已經(jīng)被回收。

該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法。

五、垃圾收集算法(垃圾回收器都是基于這些算法來(lái)實(shí)現(xiàn)) 1、標(biāo)記-清除(Mark-Sweep)算法

  這是最基礎(chǔ)的算法,標(biāo)記-清除算法就如同它的名字樣,分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。這種算法的不足主要體現(xiàn)在效率和空間,從效率的角度講,標(biāo)記和清除兩個(gè)過(guò)程的效率都不高;從空間的角度講,標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片, 內(nèi)存碎片太多可能會(huì)導(dǎo)致以后程序運(yùn)行過(guò)程中在需要分配較大對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)一次垃圾收集動(dòng)作。標(biāo)記-清除算法執(zhí)行過(guò)程如圖:

2、復(fù)制(Copying)算法

  復(fù)制算法是為了解決效率問(wèn)題而出現(xiàn)的,它將可用的內(nèi)存分為兩塊,每次只用其中一塊,當(dāng)這一塊內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已經(jīng)使用過(guò)的內(nèi)存空間一次性清理掉。這樣每次只需要對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也不需要考慮內(nèi)存碎片等復(fù)雜情況,只需要移動(dòng)指針,按照順序分配即可。復(fù)制算法的執(zhí)行過(guò)程如圖:

  不過(guò)這種算法有個(gè)缺點(diǎn),內(nèi)存縮小為了原來(lái)的一半,這樣代價(jià)太高了?,F(xiàn)在的商用虛擬機(jī)都采用這種算法來(lái)回收新生代,不過(guò)研究表明1:1的比例非常不科學(xué),因此新生代的內(nèi)存被劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。每次回收時(shí),將Eden和Survivor中還存活著的對(duì)象一次性復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過(guò)的Survivor空間。HotSpot虛擬機(jī)默認(rèn)Eden區(qū)和Survivor區(qū)的比例為8:1,意思是每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%。當(dāng)然,我們沒(méi)有辦法保證每次回收都只有不多于10%的對(duì)象存活,當(dāng)Survivor空間不夠用時(shí),需要依賴?yán)夏甏M(jìn)行分配擔(dān)保(Handle Promotion)。

3、標(biāo)記-整理(Mark-Compact)算法

  復(fù)制算法在對(duì)象存活率較高的場(chǎng)景下要進(jìn)行大量的復(fù)制操作,效率很低。萬(wàn)一對(duì)象100%存活,那么需要有額外的空間進(jìn)行分配擔(dān)保。老年代都是不易被回收的對(duì)象,對(duì)象存活率高,因此一般不能直接選用復(fù)制算法。根據(jù)老年代的特點(diǎn),有人提出了另外一種標(biāo)記-整理算法,過(guò)程與標(biāo)記-清除算法一樣,不過(guò)不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活對(duì)象都向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。標(biāo)記-整理算法的工作過(guò)程如圖:

六、垃圾收集器

  垃圾收集器就是上面講的理論知識(shí)的具體實(shí)現(xiàn)了。不同虛擬機(jī)所提供的垃圾收集器可能會(huì)有很大差別,我們使用的是HotSpot,HotSpot這個(gè)虛擬機(jī)所包含的所有收集器如圖:

  上圖展示了7種作用于不同分代的收集器,如果兩個(gè)收集器之間存在連線,那說(shuō)明它們可以搭配使用。虛擬機(jī)所處的區(qū)域說(shuō)明它是屬于新生代收集器還是老年代收集器。多說(shuō)一句,我們必須明確一個(gè)觀點(diǎn):沒(méi)有最好的垃圾收集器,更加沒(méi)有萬(wàn)能的收集器,只能選擇對(duì)具體應(yīng)用最合適的收集器。這也是HotSpot為什么要實(shí)現(xiàn)這么多收集器的原因。OK,下面一個(gè)一個(gè)看一下收集器。

Serial收集器

  最基本、發(fā)展歷史最久的收集器,這個(gè)收集器是一個(gè)采用復(fù)制算法的單線程的收集器,單線程一方面意味著它只會(huì)使用一個(gè)CPU或一條線程去完成垃圾收集工作,另一方面也意味著它進(jìn)行垃圾收集時(shí)必須暫停其他線程的所有工作,直到它收集結(jié)束為止。后者意味著,在用戶不可見(jiàn)的情況下要把用戶正常工作的線程全部停掉,這對(duì)很多應(yīng)用是難以接受的。不過(guò)實(shí)際上到目前為止,Serial收集器依然是虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器,因?yàn)樗?jiǎn)單而高效。用戶桌面應(yīng)用場(chǎng)景中,分配給虛擬機(jī)管理的內(nèi)存一般來(lái)說(shuō)不會(huì)很大,收集幾十兆甚至一兩百兆的新生代停頓時(shí)間在幾十毫秒最多一百毫秒,只要不是頻繁發(fā)生,這點(diǎn)停頓是完全可以接受的。Serial收集器運(yùn)行過(guò)程如下圖所示:

  說(shuō)明:1. 需要STW(Stop The World),停頓時(shí)間長(zhǎng)。2. 簡(jiǎn)單高效,對(duì)于單個(gè)CPU環(huán)境而言,Serial收集器由于沒(méi)有線程交互開(kāi)銷,可以獲取最高的單線程收集效率。

ParNew收集器

  ParNew收集器其實(shí)就是Serial收集器的多線程版本,除了使用多條線程進(jìn)行垃圾收集外,其余行為和Serial收集器完全一樣,包括使用的也是復(fù)制算法。ParNew收集器除了多線程以外和Serial收集器并沒(méi)有太多創(chuàng)新的地方,但是它卻是Server模式下的虛擬機(jī)首選的新生代收集器,其中有一個(gè)很重要的和性能無(wú)關(guān)的原因是,除了Serial收集器外,目前只有它能與CMS收集器配合工作(看圖)。CMS收集器是一款幾乎可以認(rèn)為有劃時(shí)代意義的垃圾收集器,因?yàn)樗谝淮螌?shí)現(xiàn)了讓垃圾收集線程與用戶線程基本上同時(shí)工作。ParNew收集器在單CPU的環(huán)境中絕對(duì)不會(huì)有比Serial收集器更好的效果,甚至由于線程交互的開(kāi)銷,該收集器在兩個(gè)CPU的環(huán)境中都不能百分之百保證可以超越Serial收集器。當(dāng)然,隨著可用CPU數(shù)量的增加,它對(duì)于GC時(shí)系統(tǒng)資源的有效利用還是很有好處的。它默認(rèn)開(kāi)啟的收集線程數(shù)與CPU數(shù)量相同,在CPU數(shù)量非常多的情況下,可以使用-XX:ParallelGCThreads參數(shù)來(lái)限制垃圾收集的線程數(shù)。ParNew收集器運(yùn)行過(guò)程如下圖所示:

Parallel Scavenge收集器

  Parallel Scavenge收集器也是一個(gè)新生代收集器,也是用復(fù)制算法的收集器,也是并行的多線程收集器,但是它的特點(diǎn)是它的關(guān)注點(diǎn)和其他收集器不同。介紹這個(gè)收集器主要還是介紹吞吐量的概念。CMS等收集器的關(guān)注點(diǎn)是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目標(biāo)則是打到一個(gè)可控制的吞吐量。所謂吞吐量的意思就是CPU用于運(yùn)行用戶代碼時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間),虛擬機(jī)總運(yùn)行100分鐘,垃圾收集1分鐘,那吞吐量就是99%。另外,Parallel Scavenge收集器是虛擬機(jī)運(yùn)行在Server模式下的默認(rèn)垃圾收集器。

  停頓時(shí)間短適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗(yàn);高吞吐量則可以高效率利用CPU時(shí)間,盡快完成運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。

  虛擬機(jī)提供了-XX:MaxGCPauseMillis和-XX:GCTimeRatio兩個(gè)參數(shù)來(lái)精確控制最大垃圾收集停頓時(shí)間和吞吐量大小。不過(guò)不要以為前者越小越好,GC停頓時(shí)間的縮短是以犧牲吞吐量和新生代空間換取的。由于與吞吐量關(guān)系密切,Parallel Scavenge收集器也被稱為“吞吐量?jī)?yōu)先收集器”。Parallel Scavenge收集器有一個(gè)-XX:+UseAdaptiveSizePolicy參數(shù),這是一個(gè)開(kāi)關(guān)參數(shù),這個(gè)參數(shù)打開(kāi)之后,就不需要手動(dòng)指定新生代大小、Eden區(qū)和Survivor參數(shù)等細(xì)節(jié)參數(shù)了,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況以及性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量。如果對(duì)于垃圾收集器運(yùn)作原理不太了解,以至于在優(yōu)化比較困難的時(shí)候,使用Parallel Scavenge收集器配合自適應(yīng)調(diào)節(jié)策略,把內(nèi)存管理的調(diào)優(yōu)任務(wù)交給虛擬機(jī)去完成將是一個(gè)不錯(cuò)的選擇。

Serial Old收集器

Serial收集器的老年代版本,同樣是一個(gè)單線程收集器,使用“標(biāo)記-整理算法”,這個(gè)收集器的主要意義也是在于給Client模式下的虛擬機(jī)使用。

Parallel Old收集器

  Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。這個(gè)收集器在JDK 1.6之后的出現(xiàn),“吞吐量?jī)?yōu)先收集器”終于有了比較名副其實(shí)的應(yīng)用組合,在注重吞吐量以及CPU資源敏感的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge收集器+Parallel Old收集器的組合。運(yùn)行過(guò)程如下圖所示:

CMS收集器

CMS(Conrrurent Mark Sweep)收集器是以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。使用標(biāo)記 - 清除算法,收集過(guò)程分為如下四步:

初始標(biāo)記,標(biāo)記GCRoots能直接關(guān)聯(lián)到的對(duì)象,時(shí)間很短。

并發(fā)標(biāo)記,進(jìn)行GCRoots Tracing(可達(dá)性分析)過(guò)程,時(shí)間很長(zhǎng)。

重新標(biāo)記,修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,時(shí)間較長(zhǎng)。

并發(fā)清除,回收內(nèi)存空間,時(shí)間很長(zhǎng)。

其中,并發(fā)標(biāo)記與并發(fā)清除兩個(gè)階段耗時(shí)最長(zhǎng),但是可以與用戶線程并發(fā)執(zhí)行。運(yùn)行過(guò)程如下圖所示:

說(shuō)明:

對(duì)CPU資源非常敏感,可能會(huì)導(dǎo)致應(yīng)用程序變慢,吞吐率下降。

無(wú)法處理浮動(dòng)垃圾,因?yàn)樵诓l(fā)清理階段用戶線程還在運(yùn)行,自然就會(huì)產(chǎn)生新的垃圾,而在此次收集中無(wú)法收集他們,只能留到下次收集,這部分垃圾為浮動(dòng)垃圾,同時(shí),由于用戶線程并發(fā)執(zhí)行,所以需要預(yù)留一部分老年代空間提供并發(fā)收集時(shí)程序運(yùn)行使用。

由于采用的標(biāo)記 - 清除算法,會(huì)產(chǎn)生大量的內(nèi)存碎片,不利于大對(duì)象的分配,可能會(huì)提前觸發(fā)一次Full GC。虛擬機(jī)提供了-XX:+UseCMSCompactAtFullCollection參數(shù)來(lái)進(jìn)行碎片的合并整理過(guò)程,這樣會(huì)使得停頓時(shí)間變長(zhǎng),虛擬機(jī)還提供了一個(gè)參數(shù)配置,-XX:+CMSFullGCsBeforeCompaction,用于設(shè)置執(zhí)行多少次不壓縮的Full GC后,接著來(lái)一次帶壓縮的GC。

G1收集器

  G1算法將堆劃分為若干個(gè)區(qū)域(Region),它仍然屬于分代收集器。不過(guò),這些區(qū)域的一部分包含新生代,新生代的垃圾收集依然采用暫停所有應(yīng)用線程的方式,將存活對(duì)象拷貝到老年代或者Survivor空間。老年代也分成很多區(qū)域,G1收集器通過(guò)將對(duì)象從一個(gè)區(qū)域復(fù)制到另外一個(gè)區(qū)域,完成了清理工作。這就意味著,在正常的處理過(guò)程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會(huì)有cms內(nèi)存碎片問(wèn)題的存在了。

  在G1中,還有一種特殊的區(qū)域,叫Humongous區(qū)域。 如果一個(gè)對(duì)象占用的空間超過(guò)了分區(qū)容量50%以上,G1收集器就認(rèn)為這是一個(gè)巨型對(duì)象。這些巨型對(duì)象,默認(rèn)直接會(huì)被分配在年老代,但是如果它是一個(gè)短期存在的巨型對(duì)象,就會(huì)對(duì)垃圾收集器造成負(fù)面影響。為了解決這個(gè)問(wèn)題,G1劃分了一個(gè)Humongous區(qū),它用來(lái)專門存放巨型對(duì)象。如果一個(gè)H區(qū)裝不下一個(gè)巨型對(duì)象,那么G1會(huì)尋找連續(xù)的H分區(qū)來(lái)存儲(chǔ)。為了能找到連續(xù)的H區(qū),有時(shí)候不得不啟動(dòng)Full GC。

G1主要有以下特點(diǎn):

并行和并發(fā)。使用多個(gè)CPU來(lái)縮短Stop The World停頓時(shí)間,與用戶線程并發(fā)執(zhí)行。

分代收集。獨(dú)立管理整個(gè)堆,但是能夠采用不同的方式去處理新創(chuàng)建對(duì)象和已經(jīng)存活了一段時(shí)間、熬過(guò)多次GC的舊對(duì)象,以獲取更好的收集效果。

空間整合?;跇?biāo)記 - 整理算法,無(wú)內(nèi)存碎片產(chǎn)生。

可預(yù)測(cè)的停頓。能簡(jiǎn)歷可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒。

  在G1之前的垃圾收集器,收集的范圍都是整個(gè)新生代或者老年代,而G1不再是這樣。使用G1收集器時(shí),Java堆的內(nèi)存布局與其他收集器有很大差別,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分(可以不連續(xù))Region的集合。

七、CMS和G1對(duì)比(過(guò)去 vs 未來(lái)) CMS垃圾回收器  CMS堆內(nèi)存結(jié)構(gòu)劃分:

新生代:eden space + 2個(gè)survivor

老年代:old space

持久代:1.8之前的perm space

元空間:1.8之后的metaspace

  注意:這些space必須是地址連續(xù)的空間

CMS中垃圾回收模式

對(duì)象分配

優(yōu)先在Eden區(qū)分配

  在JVM內(nèi)存模型一文中, 我們大致了解了VM年輕代堆內(nèi)存可以劃分為一塊Eden區(qū)和兩塊Survivor區(qū). 在大多數(shù)情況下, 對(duì)象在新生代Eden區(qū)中分配, 當(dāng)Eden區(qū)沒(méi)有足夠空間分配時(shí), VM發(fā)起一次Minor GC, 將Eden區(qū)和其中一塊Survivor區(qū)內(nèi)尚存活的對(duì)象放入另一塊Survivor區(qū)域, 如果在Minor GC期間發(fā)現(xiàn)新生代存活對(duì)象無(wú)法放入空閑的Survivor區(qū), 則會(huì)通過(guò)空間分配擔(dān)保機(jī)制使對(duì)象提前進(jìn)入老年代(空間分配擔(dān)保見(jiàn)下).

大對(duì)象直接進(jìn)入老年代

  Serial和ParNew兩款收集器提供了-XX:PretenureSizeThreshold的參數(shù), 令大于該值的大對(duì)象直接在老年代分配, 這樣做的目的是避免在Eden區(qū)和Survivor區(qū)之間產(chǎn)生大量的內(nèi)存復(fù)制(大對(duì)象一般指 需要大量連續(xù)內(nèi)存的Java對(duì)象, 如很長(zhǎng)的字符串和數(shù)組), 因此大對(duì)象容易導(dǎo)致還有不少空閑內(nèi)存就提前觸發(fā)GC以獲取足夠的連續(xù)空間.

  然而取歷次晉升的對(duì)象的平均大小也是有一定風(fēng)險(xiǎn)的, 如果某次Minor GC存活后的對(duì)象突增,遠(yuǎn)遠(yuǎn)高于平均值的話,依然可能導(dǎo)致?lián)J?Handle Promotion Failure, 老年代也無(wú)法存放這些對(duì)象了), 此時(shí)就只好在失敗后重新發(fā)起一次Full GC(讓老年代騰出更多空間).

空間分配擔(dān)保

  在執(zhí)行Minor GC前, VM會(huì)首先檢查老年代是否有足夠的空間存放新生代尚存活對(duì)象, 由于新生代使用復(fù)制收集算法, 為了提升內(nèi)存利用率, 只使用了其中一個(gè)Survivor作為輪換備份, 因此當(dāng)出現(xiàn)大量對(duì)象在Minor GC后仍然存活的情況時(shí), 就需要老年代進(jìn)行分配擔(dān)保, 讓Survivor無(wú)法容納的對(duì)象直接進(jìn)入老年代, 但前提是老年代需要有足夠的空間容納這些存活對(duì)象. 但存活對(duì)象的大小在實(shí)際完成GC前是無(wú)法明確知道的, 因此Minor GC前, VM會(huì)先首先檢查老年代連續(xù)空間是否大于新生代對(duì)象總大小或歷次晉升的平均大小, 如果條件成立, 則進(jìn)行Minor GC, 否則進(jìn)行Full GC(讓老年代騰出更多空間).

對(duì)象晉升

年齡閾值

  VM為每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(Age)計(jì)數(shù)器, 對(duì)象在Eden出生如果經(jīng)第一次Minor GC后仍然存活, 且能被Survivor容納的話, 將被移動(dòng)到Survivor空間中, 并將年齡設(shè)為1. 以后對(duì)象在Survivor區(qū)中每熬過(guò)一次Minor GC年齡就+1. 當(dāng)增加到一定程度(-XX:MaxTenuringThreshold, 默認(rèn)15), 將會(huì)晉升到老年代.

提前晉升: 動(dòng)態(tài)年齡判定

  然而VM并不總是要求對(duì)象的年齡必須達(dá)到MaxTenuringThreshold才能晉升老年代: 如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半, 年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代, 而無(wú)須等到晉升年齡.

G1垃圾回收器 G1堆內(nèi)存結(jié)構(gòu)劃分(它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域Region)

G1中提供了三種垃圾回收模式:young gc、mixed gc 和 full gc

Young GC

發(fā)生在年輕代的GC算法,一般對(duì)象(除了巨型對(duì)象)都是在eden region中分配內(nèi)存,當(dāng)所有eden region被耗盡無(wú)法申請(qǐng)內(nèi)存時(shí),就會(huì)觸發(fā)一次young gc,這種觸發(fā)機(jī)制和之前的young gc差不多,執(zhí)行完一次young gc,活躍對(duì)象會(huì)被拷貝到survivor region或者晉升到old region中,空閑的region會(huì)被放入空閑列表中,等待下次被使用。

Mixed GC

當(dāng)越來(lái)越多的對(duì)象晉升到老年代old region時(shí),為了避免堆內(nèi)存被耗盡,虛擬機(jī)會(huì)觸發(fā)一個(gè)混合的垃圾收集器,即mixed gc,該算法并不是一個(gè)old gc,除了回收整個(gè)young region,還會(huì)回收一部分的old region,這里需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進(jìn)行收集,從而可以對(duì)垃圾回收的耗時(shí)時(shí)間進(jìn)行控制。

Full GC

如果對(duì)象內(nèi)存分配速度過(guò)快,mixed gc來(lái)不及回收,導(dǎo)致老年代被填滿,就會(huì)觸發(fā)一次full gc,G1的full gc算法就是單線程執(zhí)行的serial old gc,會(huì)導(dǎo)致異常長(zhǎng)時(shí)間的暫停時(shí)間,需要進(jìn)行不斷的調(diào)優(yōu),盡可能的避免full gc.

八、各種垃圾收集器的選用

首先查看你使用的垃圾回收器是什么?

java -XX:+PrintCommandLineFlags -version

根據(jù)自身系統(tǒng)需求選擇最合適的垃圾回收器(沒(méi)有最好的,只有最是適合的)

九、總結(jié)

到此GC的內(nèi)存就差不多了,其中不免有些錯(cuò)誤的地方,或者理解有偏頗的地方歡迎大家提出來(lái)!

關(guān)于GC更細(xì)粒度的調(diào)優(yōu),沒(méi)敢妄言,今后有了實(shí)戰(zhàn)事例在補(bǔ)上?。?!

個(gè)人博客地址:

csdn:https://blog.csdn.net/tiantuo6513 

cnblogs:https://www.cnblogs.com/baixianlong

segmentfault:https://segmentfault.com/u/baixianlong

github:https://github.com/xianlongbai

本文參考:

https://www.cnblogs.com/xiaox...;/font>

https://zhuanlan.zhihu.com/p/...;/font>

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77579.html

相關(guān)文章

  • 后臺(tái)開(kāi)發(fā)常問(wèn)面試題集錦(問(wèn)題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問(wèn)題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問(wèn)題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    spacewander 評(píng)論0 收藏0
  • 后臺(tái)開(kāi)發(fā)常問(wèn)面試題集錦(問(wèn)題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問(wèn)題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問(wèn)題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    xfee 評(píng)論0 收藏0
  • 后臺(tái)開(kāi)發(fā)常問(wèn)面試題集錦(問(wèn)題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問(wèn)題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問(wèn)題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    makeFoxPlay 評(píng)論0 收藏0
  • jvm原理

    摘要:在之前,它是一個(gè)備受爭(zhēng)議的關(guān)鍵字,因?yàn)樵诔绦蛑惺褂盟占骼斫夂驮矸治龊?jiǎn)稱,是后提供的面向大內(nèi)存區(qū)數(shù)到數(shù)多核系統(tǒng)的收集器,能夠?qū)崿F(xiàn)軟停頓目標(biāo)收集并且具有高吞吐量具有更可預(yù)測(cè)的停頓時(shí)間。 35 個(gè) Java 代碼性能優(yōu)化總結(jié) 優(yōu)化代碼可以減小代碼的體積,提高代碼運(yùn)行的效率。 從 JVM 內(nèi)存模型談線程安全 小白哥帶你打通任督二脈 Java使用讀寫(xiě)鎖替代同步鎖 應(yīng)用情景 前一陣有個(gè)做...

    lufficc 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<