《深入理解Java虛擬機(jī)》——垃圾收集器與內(nèi)存分配策略問題

通過這篇文章你能知道的問題:

1.如何判斷對象是活著還是死去腿倚?

2.在Java語言中卵贱,可作為GCRoots的對象有哪些抛虫?

3.Java中引用的分類

4.對象的自救姿勢是什么西乖?

5.類在什么情況下是無用的狐榔?

6.垃圾收集算法有哪些?

7.年輕代,老年代获雕,永久代?

8.HotSpot虛擬機(jī)是如何發(fā)起內(nèi)存回收的薄腻?

9.垃圾收集器有哪些以及組合方式有哪些?

10.怎么理解GC日志?

11.內(nèi)存如何分配?

該篇文章的篇幅會有點(diǎn)長届案,希望大家耐心閱讀下去庵楷。

大家都知道內(nèi)存回收的是無用對象,但是怎么判斷對象是無用還是有用呢萝玷?

比較容易想到的是引用計數(shù)法嫁乘。那么什么是引用計數(shù)法呢?

引用計數(shù)法:給對象中添加一個引用計數(shù)器球碉,每當(dāng)有一個地方引用它時蜓斧,計數(shù)器值就加1;當(dāng)引用失效時,計數(shù)器值就減1;任何時刻計數(shù)器為0的對象就是不可能再使用的睁冬。

雖然引用計數(shù)算法實(shí)現(xiàn)簡單且判斷效率高效挎春,但是在主流的Java虛擬機(jī)里面沒有選用引用計數(shù)算法來管理內(nèi)存,其中最主要的原因是它很難解決對象間相互循環(huán)引用的問題豆拨。

既然我們學(xué)習(xí)的是Java虛擬機(jī)相關(guān)的內(nèi)容直奋,那我們就要了解Java虛擬機(jī)的實(shí)現(xiàn)中是怎么判斷對象是否存活的?

可達(dá)性分析算法:在主流的商用程序語言的主流實(shí)現(xiàn)中施禾,都是稱通過可達(dá)性分析來判斷對象是否存活的脚线。這個算法的基本思路就是通過一系列的稱為"GC Roots"的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索弥搞,搜索所走過的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時邮绿,則證明此對象是不可用的。如圖3-1所示:

在Java語言中攀例,可作為GC Roots的對象包括下面幾種:

虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象

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

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

本地方法棧中JNI(即一般說的Native方法)引用的對象

無論是通過引用計數(shù)算法判斷對象的引用數(shù)量船逮,還是通過可達(dá)性分析算法判斷對象的引用鏈?zhǔn)欠窨蛇_(dá),判斷對象是否存活都與"引用"有關(guān)粤铭。

在JDK1.2之后挖胃,Java對引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)酱鸭、弱引用(Weak Reference)吗垮、虛引用(Phantom Reference)4種,這4種引用強(qiáng)度依次逐漸減弱。

強(qiáng)引用就是指在程序代碼之中普遍存在的凛辣,類似"Object obj=new Object()"這類的引用抱既,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象扁誓。

軟引用是用來描述一些還有用但并非必需的對象防泵。對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前蝗敢,將會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收捷泞。如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常寿谴。

弱引用也是用來描述非必需對象的锁右,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前讶泰。當(dāng)垃圾收集器工作時咏瑟,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象痪署。

虛引用也被稱為幽靈引用或者幻影引用码泞,它是最弱的一種引用關(guān)系。一個對象是否有虛引用的存在狼犯,完全不會對其生存時間構(gòu)成影響余寥,也無法通過虛引用來取得一個對象實(shí)例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知悯森。

上面介紹了對象存活的判斷以及引用的分類宋舷,那么對象如何在被殺死時來個自我救贖呢?

救贖之前我們要了解我們的對象是怎么被殺死的:

在可達(dá)性分析算法中不可達(dá)的對象瓢姻,也并非是“非死不可”的祝蝠,這時候它們暫時處于“緩刑”階段,要真正宣告一個對象死亡幻碱,至少要經(jīng)歷兩次標(biāo)記過程——如果對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈续膳,那它將會被第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是此對象是否有必要執(zhí)行finalize()方法收班。當(dāng)對象沒有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過谒兄,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”摔桦。(那么問題來了,這種情況下的二次標(biāo)記在哪里?)

如果這個對象被判定為有必要執(zhí)行finalize()方法邻耕,那么這個對象將會放置在一個叫做F-Queue的隊(duì)列之中鸥咖,并在稍后由一個由虛擬機(jī)自動建立的、低優(yōu)先級的Finalizer線程去執(zhí)行它兄世。這里所謂的"執(zhí)行"是指虛擬機(jī)會觸發(fā)這個方法啼辣,但并不承諾會等待它運(yùn)行結(jié)束,這樣做的原因是御滩,如果一個對象在finalize()方法中執(zhí)行緩慢鸥拧,或者發(fā)生了死循環(huán),將很可能會導(dǎo)致F-Queue隊(duì)列中其他對象永久處于等待,甚至導(dǎo)致整個內(nèi)存回收系統(tǒng)崩潰削解。finalize()方法是對象逃脫死亡命運(yùn)的最后一次機(jī)會富弦,稍后GC將對F-Queue中的對象進(jìn)行第二次小規(guī)模標(biāo)記,如果對象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關(guān)聯(lián)即可氛驮,譬如把自己(this關(guān)鍵字)賦值給某個類變量或者對象的成員變量腕柜,那在第二次標(biāo)記時它將被移除出"即將回收"的集合;如果對象這時候還沒有逃脫矫废,那基本上它就真的被回收了盏缤。

[java]view plaincopy

packagecom.general.android.test.oom;

publicclassTestFinalize?{

publicstaticTestFinalize?SAVE_HOOK=null;

publicvoidisAlive(){

System.out.println("yes,?i?am?still?alive");

}

@Override

protectedvoidfinalize()throwsThrowable?{

//?TODO?Auto-generated?method?stub

super.finalize();

System.out.println("finalize?method?executed");

TestFinalize.SAVE_HOOK=this;

}

publicstaticvoidmain(String[]?args)throwsInterruptedException?{

SAVE_HOOK=newTestFinalize();

//對象第一次成功拯救自己

SAVE_HOOK=null;

System.gc();

Thread.sleep(500);

if(SAVE_HOOK!=null){

SAVE_HOOK.isAlive();

}else{

System.out.println("no,I?am?dead");

}

SAVE_HOOK=null;

System.gc();

Thread.sleep(500);

if(SAVE_HOOK!=null){

SAVE_HOOK.isAlive();

}else{

System.out.println("no,I?am?dead");

}

}

}

上面代碼的輸出結(jié)果:

finalize method executed

yes, i am still alive

no,I am dead

注意:任何一個對象的finalize()方法都只會被系統(tǒng)自動調(diào)用一次,如果對象面臨下一次回收蓖扑,它的finalize()方法不會被再次執(zhí)行唉铜。

在方法區(qū)中類需要同時滿足下面3個條件才能算是“無用的類”:

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

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

3.該類對應(yīng)的java.ang.Class對象沒有在任何地方被引用赵誓,無法在任何地方通過反射訪問該類的方法打毛。

虛擬機(jī)可以對滿足上述3個條件的無用類進(jìn)行回收,這里說的僅僅是“可以”俩功,而并不是和對象一樣幻枉,不使用了就必然會回收。是否對類進(jìn)行回收诡蜓,HotSpot虛擬機(jī)提供了-Xnoclassgc參數(shù)進(jìn)行控制熬甫,還可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看類加載和卸載信息蔓罚,其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虛擬機(jī)中使用椿肩,-XX:+TraceClassUnLoading參數(shù)需要FastDebug版的虛擬機(jī)支持。

常用的垃圾收集算法有哪些豺谈?

常用的垃圾收集算法有:標(biāo)記-清除算法郑象、復(fù)制算法、標(biāo)記-整理算法茬末、分代收集算法厂榛。

標(biāo)記-清除算法:算法分為“標(biāo)記”和“清除”兩個階段:首先標(biāo)記出所有需要回收的對象盖矫,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。標(biāo)記-清除算法主要有兩個不足:(1)一個是效率問題击奶,標(biāo)記和清除兩個過程的效率都不高辈双;另一個是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片柜砾,空間碎片太多可能會導(dǎo)致以后在程序運(yùn)行過程中需要分配較大對象時湃望,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。

復(fù)制算法:復(fù)制算法將可用內(nèi)存按容量劃分為大小相等的兩塊痰驱,每次只使用其中的一塊证芭。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另一塊上面萄唇,然后再把已使用過的內(nèi)存空間一次清理掉檩帐。這樣使得每次都是對整個半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況另萤,只要移動堆頂指針湃密,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡單四敞,運(yùn)行高效泛源。只是這種算法的代價是將內(nèi)存縮小為了原來的一半,未免太高了一點(diǎn)忿危。

現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法來回收新生代达箍,IBM公司的專門研究表明,新生代中的對象98%是"朝生夕死"的铺厨,所以并不需要按照1:1的比例來劃分內(nèi)存空間缎玫,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor解滓。當(dāng)回收時赃磨,將Eden和Survivor中還存活的對象一次性地復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間洼裤。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1:1邻辉,也就是每次新生代中可用內(nèi)存空間為整個新生代容量的90%(80%+10%),只有10%的內(nèi)存會被“浪費(fèi)”腮鞍。當(dāng)然值骇,98%的對象可回收只是一般場景下的數(shù)據(jù),我們沒有辦法保證每次回收都只有不多于10%的對象存活移国,當(dāng)Survivor空間不夠用時吱瘩,需要依賴其他內(nèi)存(這里指老年代)進(jìn)行分配擔(dān)保,也就是說如果另外一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象時迹缀,這些對象將直接通過分配擔(dān)保機(jī)制進(jìn)入老年代使碾。

標(biāo)記-整理算法:復(fù)制收集算法在對象存活率較高時就要進(jìn)行較多的復(fù)制操作皱卓,效率將會變低。更關(guān)鍵的是部逮,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保嫂易,以應(yīng)對被使用的內(nèi)存中所有對象都100%存活的極端情況兄朋,所以在老年代一般不能直接選用這種算法。根據(jù)老年代的特點(diǎn)怜械,有人提出了另外一種“標(biāo)記-整理”算法颅和,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理缕允,而是讓所有存活的對象都向一端移動峡扩,然后直接清理掉端邊界以外的內(nèi)存。

分代收集算法:當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用"分代收集"算法障本,這種算法并沒有什么新的思想教届,只是根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊。一般把Java堆分為新生代和老年代驾霜,這樣就可以根據(jù)各個年代的特點(diǎn)采用最恰當(dāng)?shù)氖占惴ò秆怠T谛律校看卫占瘯r都發(fā)現(xiàn)有大批對象死去粪糙,只有少量存活强霎,那就選用復(fù)制算法,只需要付出少量存活對象的復(fù)制成本就可以完成收集蓉冈。而老年代中因?yàn)閷ο蟠婊盥矢叱俏瑁瑳]有額外空間對它進(jìn)行分配擔(dān)保,就必須使用標(biāo)記-清理或者標(biāo)記-整理算法來進(jìn)行回收寞酿。

這里提到了新生代和老年代家夺,那么新生代是什么?老年代又是什么呢熟嫩?永久代又是何物呢秦踪?

新生代

主要是用來存放新生的對象,即剛剛創(chuàng)建的對象掸茅。新生代又被劃分為Eden區(qū)和Survivor區(qū)(包含空間相等的From椅邓、To區(qū),沒有先后順序昧狮,是復(fù)制算法的需要)景馁。大多數(shù)情況下,java中新建的對象都是在新生代上分配的逗鸣,通過復(fù)制算法來進(jìn)行分配內(nèi)存和垃圾回收合住。

老年代

主要存放應(yīng)用程序中生命周期長的內(nèi)存對象绰精。在新生代中經(jīng)過多次垃圾回收仍然存活的對象,會被存放到老年代中透葛。老年代通過標(biāo)記-整理算法來清理無用內(nèi)存笨使。

永久代

永久代這個概念比較特殊,因?yàn)橛谰么荋otspot虛擬機(jī)特有的概念僚害,是方法區(qū)的一種實(shí)現(xiàn)喉脖,別的JVM都沒有這個東西猾骡。但是在jdk1.8中仪召,永久代已經(jīng)被移除担猛。

在Java 6中,方法區(qū)中包含的數(shù)據(jù)岳遥,除了JIT編譯生成的代碼存放在native memory的CodeCache區(qū)域奕翔,其他都存放在永久代;

在Java 7中浩蓉,Symbol的存儲從PermGen移動到了native memory派继,并且把靜態(tài)變量從instanceKlass末尾(位于PermGen內(nèi))移動到了java.lang.Class對象的末尾(位于普通Java heap內(nèi));

在Java 8中妻往,永久代被徹底移除互艾,取而代之的是另一塊與堆不相連的本地內(nèi)存——元空間(Metaspace),?XX:MaxPermSize 參數(shù)失去了意義,取而代之的是-XX:MaxMetaspaceSize讯泣。

http://blog.csdn.net/ooppookid/article/details/51477147

https://www.zhihu.com/question/49044988/answer/113961406

HotSpot是如何去發(fā)起內(nèi)存回收的纫普?

前面說過HotSpot虛擬機(jī)是通過可達(dá)性分析來判斷對象是否存活的。而垃圾收集算法在標(biāo)記可回收對象時好渠,肯定也是使用了可達(dá)性分析來判斷哪些對象是可以被回收昨稼。大家都知道可作為GC Roots的節(jié)點(diǎn)主要在全局性的引用(例如常量或類靜態(tài)屬性)與執(zhí)行上下文(例如棧幀中的本地變量表)中,現(xiàn)在很多應(yīng)用僅僅方法區(qū)就有數(shù)百兆拳锚,如果要逐個檢查這里面的引用假栓,那么必然會消耗很多時間。另外霍掺,可達(dá)性分析對執(zhí)行時間的敏感還體現(xiàn)在GC停頓上匾荆,因?yàn)檫@項(xiàng)分析工作必須在一個能確保一致性的快照中進(jìn)行——這里"一致性"的意思是指整個分析期間整個執(zhí)行系統(tǒng)看起來就像被凍結(jié)在某個時間點(diǎn)上,不可以出現(xiàn)分析過程中對象引用關(guān)系還在不斷變化的情況杆烁,該點(diǎn)不滿足的話分析結(jié)果準(zhǔn)確性就無法得到保證牙丽。這點(diǎn)是導(dǎo)致GC進(jìn)行時必須停頓所有Java執(zhí)行線程的其中一個重要原因,即使是在號稱不會發(fā)生停頓的CMS收集器中兔魂,枚舉根節(jié)點(diǎn)時也是必須要停頓的烤芦。

由于目前的主流Java虛擬機(jī)使用的都是準(zhǔn)確式GC,所以當(dāng)執(zhí)行系統(tǒng)停頓下來后析校,并不需要一個不漏地檢查完所有執(zhí)行上下文和全局的引用位置构罗,虛擬機(jī)應(yīng)當(dāng)是有辦法直接得知哪些地方存放著對象引用铜涉。在HotSpot的實(shí)現(xiàn)中,是使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)來達(dá)到這個目的遂唧,在類加載完成的時候芙代,HotSpot就把對象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計算出來,在JIT編譯過程中盖彭,也會在特定的位置記錄下棧和寄存器中哪些位置是引用链蕊。這些位置稱為安全點(diǎn)(Safepoint),即程序執(zhí)行時并非在所有地方都能停頓下來開始GC谬泌,只有在到達(dá)安全點(diǎn)時才能暫停。Safepoint的選定既不能太少以致于讓GC等待時間太長逻谦,也不能過于頻繁以致于過分增大運(yùn)行時的負(fù)荷掌实。所以,安全點(diǎn)的選定基本上是以程序“是否具有讓程序長時間執(zhí)行的特征”為標(biāo)準(zhǔn)進(jìn)行選定的邦马。對于安全點(diǎn)贱鼻,另一個需要考慮的問題是如何在GC發(fā)生時讓所有線程(這里不包括執(zhí)行JNI調(diào)用的線程)都“跑”到最近的安全點(diǎn)上再停頓下來。這里有兩種方案可供選擇:搶先式中斷和主動式中斷滋将。

搶先式中斷:不需要線程的執(zhí)行代碼主動去配合邻悬,在GC發(fā)生時,首先把所有線程全部中斷随闽,如果發(fā)現(xiàn)有線程中斷的地方不在安全點(diǎn)上父丰,就恢復(fù)線程,讓它“跑”到安全點(diǎn)上掘宪。但是現(xiàn)在幾乎沒有虛擬機(jī)實(shí)現(xiàn)采用搶先式中斷來暫停線程從而響應(yīng)GC事件蛾扇。

主動式中斷:當(dāng)GC需要中斷線程的時候,不直接對線程操作魏滚,僅僅簡單地設(shè)置一個標(biāo)志镀首,各個線程執(zhí)行時主動去輪詢這個標(biāo)志,發(fā)現(xiàn)中斷標(biāo)志為真時就自己中斷掛起鼠次。輪詢標(biāo)志的地方和安全點(diǎn)是重合的更哄,另外再加上創(chuàng)建對象需要分配內(nèi)存的地方。

使用安全點(diǎn)似乎已經(jīng)完美地解決了如何進(jìn)入GC的問題腥寇,但是實(shí)際情況卻并不一定成翩。安全點(diǎn)機(jī)制保證了程序執(zhí)行時,在不太長的時間內(nèi)就會遇到可進(jìn)入GC的Safepoint花颗。但是在程序不執(zhí)行的時候就無法做到這一點(diǎn)捕传,比如線程在休眠或阻塞狀態(tài)。對于這種情況扩劝,就需要安全區(qū)域(Safe Region)來解決庸论。

安全區(qū)域是指在一段代碼片段之中职辅,引用關(guān)系不會發(fā)生變化。在這個區(qū)域中的任意地方開始GC都是安全的聂示。我們也可以把Safe Region看做是被擴(kuò)展了的Safepoint域携。

在線程執(zhí)行到Safe Region中的代碼時,首先標(biāo)識自己已經(jīng)進(jìn)入了Safe Region鱼喉,那樣秀鞭,當(dāng)在這段時間里JVM要發(fā)起GC時,就不用管標(biāo)識自己為Safe Region狀態(tài)的線程了扛禽。在線程要離開SafeRegion時锋边,它要檢查系統(tǒng)是否已經(jīng)完成了根節(jié)點(diǎn)枚舉(或者是整個GC過程),如果完成了编曼,那線程就繼續(xù)執(zhí)行豆巨,否則它就必須等待直到收到可以安全離開Safe Region的信號為止。

這個闡述有點(diǎn)長掐场,我來簡要概括一下:在OopMap的協(xié)助下往扔,HotSpot快速且準(zhǔn)確地完成GC Roots枚舉,并在安全點(diǎn)或者安全區(qū)域進(jìn)行中斷熊户,以保證在根結(jié)點(diǎn)枚舉過程中引用關(guān)系不發(fā)生改變萍膛,也就是在標(biāo)記可回收對象時,內(nèi)存中的引用關(guān)系不再發(fā)生變化嚷堡,這樣的操作才有意義蝗罗。

垃圾收集器有哪些以及組合方式有哪些?

如果說收集算法是內(nèi)存回收的方法論蝌戒,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)绿饵。Java虛擬機(jī)規(guī)范中對垃圾收集器應(yīng)該如何實(shí)現(xiàn)并沒有任何規(guī)定,因此不同的廠商瓶颠、不同的版本的虛擬機(jī)所提供的垃圾收集器都可能會有很大的差別拟赊,并且一般都會提供參數(shù)供用戶自己根據(jù)自己的應(yīng)用特點(diǎn)和要求組合出各個年代所使用的收集器。

這里所討論的收集器是基于JDK1.7Update 14之后的HotSpot虛擬機(jī)粹淋,上圖展示了7種作用于不同分代的收集器吸祟,如果兩個收集器之間存在連線,就說明它們可以搭配使用桃移。虛擬機(jī)所處的區(qū)域屋匕,則表示它是屬于新生代收集器還是老年代收集器。

Serial收集器

Serial收集器是最基本借杰、發(fā)展歷史最悠久的收集器过吻,曾經(jīng)(在JDK1.3.1之前)是虛擬機(jī)新生代收集的唯一選擇。大家看名字就會知道,這個收集器是一個單線程的收集器纤虽,但它的“單線程”的意義并不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作乳绕,更重要的是在它進(jìn)行垃圾收集時,必須暫停其他所有的工作線程逼纸,直到它收集結(jié)束洋措。Serial收集器是虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器,它有著優(yōu)于其他收集器的地方:簡單而高效(與其他收集器的單線程比)杰刽,對于限定單個CPU的環(huán)境來說菠发,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率贺嫂。

ParNew收集器

ParNew收集器其實(shí)就是Serial收集器的多線程版本滓鸠,除了使用多條線程進(jìn)行垃圾收集之外,其余行為包括Serial收集器可用的所有控制參數(shù)第喳、收集算法哥力、Stop The World、對象分配規(guī)則墩弯、回收策略等都與Serial收集器完全一樣,在實(shí)現(xiàn)上寞射,這兩種收集器也共用了相當(dāng)多的代碼渔工。ParNew收集器是許多運(yùn)行在Server模式下的虛擬機(jī)中首先的新生代收集器,其中有一個與性能無關(guān)但很重要的原因是桥温,除了Serial收集器外引矩,目前只有它能與CMS收集器配合工作。

在談?wù)摾占鞯纳舷挛恼Z境中侵浸,并行和并發(fā)的概念如下:

并行:指多余垃圾收集線程并行工作旺韭,但此時用戶線程仍然處于等待狀態(tài)。

并發(fā):指用戶線程與垃圾收集線程同時執(zhí)行(但不一定是并行的掏觉,可能會交替執(zhí)行)区端,用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個CPU上澳腹。

Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器织盼,它也是使用復(fù)制算法的收集器,又是并行的多線程收集器酱塔。Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同沥邻,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個可控制的吞吐量(Throughput)羊娃。所謂吞吐量就是CPU用于運(yùn)行用戶代碼的時間與CPU總消耗時間的比值唐全,即吞吐量=運(yùn)行用戶代碼時間/(運(yùn)行用戶代碼時間+垃圾收集時間),虛擬機(jī)總共運(yùn)行了100分鐘蕊玷,其中垃圾收集花掉1分鐘邮利,那吞吐量就是99%弥雹。停頓時間越短就越適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗(yàn)近弟,而高吞吐量則可以高效率地利用CPU時間缅糟,盡快完成程序的運(yùn)算任務(wù),主要適合在后臺運(yùn)算而不需要太多交互的任務(wù)祷愉。

Parallel Scavenge的一些參數(shù):

MaxGCPauseMillis:控制最大垃圾收集停頓時間窗宦,參數(shù)允許的值是一個大于0的毫秒數(shù),收集器將盡可能地保證內(nèi)存回收花費(fèi)的時間不超過設(shè)定值二鳄。不過大家不要認(rèn)為如果把這個參數(shù)的值設(shè)置得稍小一點(diǎn)就能使得系統(tǒng)的垃圾收集速度變得更快赴涵,GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取的。

GCTimeRatio:控制吞吐量的大小订讼,參數(shù)的值應(yīng)當(dāng)是一個大于0且小于100的整數(shù)髓窜,也就是垃圾收集時間占總時間的比率,相當(dāng)于是吞吐量的倒數(shù)欺殿。如果把此參數(shù)設(shè)置為N寄纵,那允許的最大GC時間就占總時間的(1/(1+N))%。比如把此參數(shù)設(shè)置為19脖苏,那允許的最大GC時間就占總時間的5%(即1/(1+19)).

UseAdaptiveSizePolicy:這是一個開關(guān)參數(shù)程拭,當(dāng)這個參數(shù)打開之后,就不需要手工指定新生代的大小(-Xmn)棍潘、Eden與Survivor區(qū)的比例(-XX:SurvivorRatio)恃鞋、晉升老年代對象大小(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù)了,虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息亦歉,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時間或者最大的吞吐量恤浪,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略。自適應(yīng)調(diào)節(jié)策略也是Parallel Scavenge收集器與ParNew收集器的一個重要區(qū)別肴楷。

Serial Old收集器

Serial Old是Serial 收集器的老年代版本水由,它同樣是一個單線程收集器,使用"標(biāo)記-整理"算法赛蔫。這個收集器的主要意義也是在于給Client模式下的虛擬機(jī)使用绷杜。如果在Server模式下,那么它主要還有兩大用途:一種用途是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用濒募,另一種用途就是作為CMS收集器的后備預(yù)案鞭盟,在并發(fā)收集發(fā)生Concurrent Mode Failure時使用。

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本瑰剃,使用多線程和標(biāo)記-整理算法齿诉。這個收集器是在JDK1.6中才開始提供的。在注重吞吐量以及CPU資源敏感的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器粤剧。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器歇竟。目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度抵恋,希望系統(tǒng)停頓時間最短焕议,以給用戶帶來較好的體驗(yàn)。CMS收集器就非常符合這類應(yīng)用的需求弧关。從名字(包含"Mark Sweep")上就可以看出盅安,CMS收集器是基于"標(biāo)記-清除"算法實(shí)現(xiàn)的,它的運(yùn)作過程相對于前面幾種收集器來說更復(fù)雜一些世囊,整個過程分為4個步驟别瞭,包括:

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

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

重新標(biāo)記(CMS remark)

并發(fā)清除(CMS concurrent sweep)

其中,初始標(biāo)記株憾、重新標(biāo)記這兩個步驟仍然需要"Stop The World"蝙寨。初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,速度很快嗤瞎,并發(fā)標(biāo)記階段就是進(jìn)行GC Roots Tracing 的過程墙歪,而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個階段的停頓時間一般會比初始標(biāo)記階段稍長一些贝奇,但遠(yuǎn)比并發(fā)標(biāo)記的時間短虹菲。由于整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程收集器收集線程都可以與用戶線程一起工作,所以弃秆,從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的髓帽。

CMS收集器有3個明顯的缺點(diǎn):

1.CMS收集器對CPU資源非常敏感菠赚。其實(shí),面向并發(fā)設(shè)計的程序都對CPU資源比較敏感郑藏。在并發(fā)階段衡查,它雖然不會導(dǎo)致用戶線程停頓,但是會因?yàn)檎加昧艘徊糠志€程而導(dǎo)致應(yīng)用程序變慢必盖,總吞吐量會降低拌牲。

2.CMS收集器無法處理浮動垃圾,可能出現(xiàn)"Concurrent Mode Failure"失敗而導(dǎo)致另一次Full GC的產(chǎn)生歌粥。由于CMS并發(fā)清理階段用戶線程還在運(yùn)行著塌忽,伴隨程序運(yùn)行自然就還會有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過程之后失驶,CMS無法在當(dāng)次收集中處理掉它們土居,只好留待下一次GC時再清理掉。這一部分垃圾就稱為"浮動垃圾"。

3.空間碎片:CMS是一款基于標(biāo)記-清除算法實(shí)現(xiàn)的收集器擦耀,所有會有空間碎片的現(xiàn)象棉圈,當(dāng)空間碎片過多時,將會給大對象分配帶來很大麻煩眷蜓,往往會出現(xiàn)老年代還有很大空間剩余分瘾,但是

無法找到足夠大的連續(xù)空間來分配當(dāng)前對象,不得不提前觸發(fā)一次Full GC吁系。

G1收集器

G1收集器是當(dāng)今收集器技術(shù)發(fā)展的最前沿成果之一德召,G1是一款面向服務(wù)端應(yīng)用的垃圾收集器,HotSpot開發(fā)團(tuán)隊(duì)賦予它的使命是未來可以替換掉JDK1.5中發(fā)布的CMS收集器垮抗。在G1之前的其他收集器進(jìn)行收集的范圍都是整個新生代或者老年代氏捞,而G1不再是這樣。使用G1收集器時冒版,Java堆的內(nèi)存布局就與其他收集器有很大差別液茎,它將整個Java堆劃分為多個大小相等的獨(dú)立區(qū)域(Region),雖然還保留有新生代和老年代的概念辞嗡,但新生代和老年代不再是物理隔離的了捆等,它們都是一部分Region(不需要連續(xù))的集合。與其他GC收集器相比续室,G1具備如下特點(diǎn):

(1)并行與并發(fā):G1能充分利用多CPU栋烤,多核環(huán)境下的硬件優(yōu)勢,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓時間挺狰,部分其他收集器原本需要停頓Java線程執(zhí)行的GC動作明郭,G1收集器仍然可以通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行。

(2)分代收集:與其他收集器一樣丰泊,分代概念在G1中依然得以保留薯定。雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個GC堆,但它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間瞳购、熬過多次GC的舊對象以獲取更好的收集效果话侄。

(3)空間整合:與CMS的“標(biāo)記-清理”算法不同,G1從整體來看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器学赛,從局部(兩個Region之間)上來看是基于“復(fù)制”算法實(shí)現(xiàn)的年堆,但無論如何,這兩種算法都意味著G1運(yùn)作期間不會產(chǎn)生內(nèi)存空間碎片盏浇,收集后能提供規(guī)整的可用內(nèi)存变丧。這種特性有利于程序長時間運(yùn)行,分配大對象時不會因?yàn)闊o法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC绢掰。

(4)可預(yù)測的停頓:這是G1相對于CMS的另一大優(yōu)勢锄贷,降低停頓時間是G1和CMS共同的關(guān)注點(diǎn)译蒂,但G1除了追求低停頓外,還能建立可預(yù)測的停頓時間模型谊却,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi)柔昼,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經(jīng)是實(shí)時Java的垃圾收集器的特征了炎辨。

G1收集器之所以能建立可預(yù)測的停頓時間模型捕透,是因?yàn)樗梢杂杏媱澋乇苊庠谡麄€Java堆中進(jìn)行全區(qū)域的垃圾收集。G1跟蹤各個Region里面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經(jīng)驗(yàn)值)碴萧,在后臺維護(hù)一個優(yōu)先列表乙嘀,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的Region(這也就是Garbage-First名稱的來由)破喻。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級的區(qū)域回收方式虎谢,保證了G1收集器在有限的時間內(nèi)可以獲取盡可能高的收集效率。

在G1收集器中曹质,Region之間的對象引用以及其他收集器中的新生代與老年代之間的對象引用婴噩,虛擬機(jī)都是使用Remembered Set來避免全堆掃描的。G1中每個Region都有一個與之對應(yīng)的Remembered Set,虛擬機(jī)發(fā)現(xiàn)程序在對Reference類型的數(shù)據(jù)進(jìn)行寫操作時羽德,會產(chǎn)生一個Write Barrier暫時中斷寫操作几莽,檢查Reference引用的對象是否處于不同的Region之中(在分代的例子中就是檢查是否老年代中的對象引用了新生代中的對象),如果是宅静,便通過CardTable把相關(guān)引用信息記錄到被引用對象所屬的Region的Remembered Set之中章蚣。當(dāng)進(jìn)行內(nèi)存回收時,在GC根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏姨夹。

如果不計算維護(hù)Remembered Set的操作纤垂,G1收集器的運(yùn)作大致可劃分為以下幾個步驟:

初始標(biāo)記(Initial Marking)

并發(fā)標(biāo)記(Concurrent Marking)

最終標(biāo)記(Final Marking)

篩選回收(Live Data Counting and Evacuation)

對CMS收集器運(yùn)作過程熟悉的讀者,一定已經(jīng)發(fā)現(xiàn)G1的前幾個步驟的運(yùn)作過程和CMS有很多相似之處磷账。初始標(biāo)記階段僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象峭沦,并且修改TAMS的值,讓下一階段用戶程序并發(fā)運(yùn)行時够颠,能在正確可用的Region中創(chuàng)建新對象熙侍,這階段需要停頓線程榄鉴,但耗時很短履磨。并發(fā)標(biāo)記階段是從GC Root開始對堆中對象進(jìn)行可達(dá)性分析,找出存活的對象庆尘,這階段耗時較長剃诅,但可與用戶程序并發(fā)執(zhí)行。而最終標(biāo)記階段則是為了修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分標(biāo)記記錄驶忌,虛擬機(jī)將這段時間對象變化記錄在線程Remembered Set Logs里面矛辕,最終標(biāo)記階段需要把Remmbered Set Logs的數(shù)據(jù)合并到Remembered Set中笑跛,這階段需要停頓線程,但是可并行執(zhí)行聊品。最后篩選回收階段首先對各個Region的回收價值和成本進(jìn)行排序飞蹂,根據(jù)用戶所期望的GC停頓時間來制定回收計劃。

關(guān)于垃圾收集器暫時就了解這么多內(nèi)容翻屈,關(guān)于可用的組合圖3-5一目了然陈哑。

那么如何閱讀GC日志呢?

想要閱讀GC日志,首先必須要開啟GC日志伸眶,開啟方式如下圖:

[java]view plaincopy

packagecom.general.garbage;

publicclassTestGCLog?{

privateObject[]?obj;

publicstaticvoidmain(String[]?args)?{

TestGCLog?gcLog=newTestGCLog();

gcLog.createObjects();

gcLog.clear();

System.gc();

}

publicvoidcreateObjects(){

obj=newObject[]{newbyte[1024*1024],newbyte[1024*1024]};

}

publicvoidclear(){

obj=null;

}

}

上面代碼運(yùn)行之后惊窖,打印出的相關(guān)的GC信息如下:

[java]view plaincopy

0.104:?[GC?(System.gc())?[PSYoungGen:?3922K->616K(36352K)]?3922K->624K(119808K),0.0035976secs]?[Times:?user=0.00sys=0.00,?real=0.00secs]

0.108:?[Full?GC?(System.gc())?[PSYoungGen:?616K->0K(36352K)]?[ParOldGen:?8K->519K(83456K)]?624K->519K(119808K),?[Metaspace:?2574K->2574K(1056768K)],0.0061018secs]?[Times:?user=0.00sys=0.00,?real=0.01secs]

Heap

PSYoungGen??????total?36352K,?used?312K?[0x00000000d7980000,0x00000000da200000,0x0000000100000000)

eden?space?31232K,1%?used?[0x00000000d7980000,0x00000000d79ce2b8,0x00000000d9800000)

from?space?5120K,0%?used?[0x00000000d9800000,0x00000000d9800000,0x00000000d9d00000)

to???space?5120K,0%?used?[0x00000000d9d00000,0x00000000d9d00000,0x00000000da200000)

ParOldGen???????total?83456K,?used?519K?[0x0000000086c00000,0x000000008bd80000,0x00000000d7980000)

object?space?83456K,0%?used?[0x0000000086c00000,0x0000000086c81ef0,0x000000008bd80000)

Metaspace???????used?2581K,?capacity?4486K,?committed?4864K,?reserved?1056768K

classspace????used?285K,?capacity?386K,?committed?512K,?reserved?1048576K

下面我們看看如何理解這段GC日志,

[GC (System.gc()) [PSYoungGen: 3922K->616K(36352K)] 3922K->624K(119808K), 0.0035976 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[Full GC (System.gc()) [PSYoungGen: 616K->0K(36352K)] [ParOldGen: 8K->519K(83456K)] 624K->519K(119808K), [Metaspace: 2574K->2574K(1056768K)], 0.0061018 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

GC和Full GC表示垃圾回收的停頓類型

PSYoungGen:表示新生代使用的是Parallel Scavenge垃圾收集器

3922K(gc前新生代所用內(nèi)存)->616K(gc后新生代所用內(nèi)存)(36352K(新生代總內(nèi)存))

3922K(gc前Java堆已使用內(nèi)存)->624K(gc后Java堆已使用內(nèi)存)(119808K(Java堆總?cè)萘?)

0.0035976 secs:表示GC所占用時間厘贼。

下面看下兩幅圖界酒,就對GC日志一目了然了。

上面兩幅圖來源于:http://blog.csdn.net/wanglha/article/details/48713217

內(nèi)存如何分配的嘴秸?

對象的內(nèi)存分配毁欣,往大方向講,就是在堆上分配(但也可能經(jīng)過JIT編譯后被拆散為標(biāo)量類型并間接地棧上分配赁遗,這點(diǎn)屬于JVM編譯優(yōu)化的操作署辉,這個會在后續(xù)的博客中說明),對象主要分配在新生代的Eden區(qū)上岩四,如果啟動了本地線程分配緩沖哭尝,將按線程優(yōu)先在TLAB上分配。少數(shù)情況下也可能會直接分配在老年代中剖煌,分配的規(guī)則并不是百分之百固定的材鹦,其細(xì)節(jié)取決于當(dāng)前使用的是哪一種垃圾收集器組合,還有虛擬機(jī)中與內(nèi)存相關(guān)的參數(shù)的設(shè)置耕姊。

對象優(yōu)先在Eden分配:大多數(shù)情況下桶唐,對象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時茉兰,虛擬機(jī)將發(fā)起一次Minor GC尤泽。

[java]view plaincopy

privatestaticfinalint_1MB=1024*1024;

/*-XX:+PrintGCTimeStamps?-XX:+PrintGCDetails???-Xms20M?-Xmx20M?-Xmn7M?-XX:SurvivorRatio=8*/

publicstaticvoidtestAllocation(){

byte[]?allocation1,allocation2,allocation3,allocation4;

allocation1=newbyte[2*_1MB];

allocation2=newbyte[2*_1MB];

allocation3=newbyte[2*_1MB];//發(fā)生一次Minor?GC

allocation4=newbyte[4*_1MB];//最后在allocation3在新生代里,其余4個對象在老年代里

}

0.111:?[GC?(Allocation?Failure)?[PSYoungGen:?4937K->504K(6656K)]?4937K->4672K(19968K),0.0041380secs]?[Times:?user=0.05sys=0.00,?real=0.00secs]

Heap

PSYoungGen??????total?6656K,?used?2613K?[0x00000000ff900000,0x0000000100000000,0x0000000100000000)

eden?space?6144K,34%?used?[0x00000000ff900000,0x00000000ffb0f748,0x00000000fff00000)

from?space?512K,98%?used?[0x00000000fff00000,0x00000000fff7e010,0x00000000fff80000)

to???space?512K,0%?used?[0x00000000fff80000,0x00000000fff80000,0x0000000100000000)

ParOldGen???????total?13312K,?used?8264K?[0x00000000fec00000,0x00000000ff900000,0x00000000ff900000)

object?space?13312K,62%?used?[0x00000000fec00000,0x00000000ff412030,0x00000000ff900000)

Metaspace???????used?2581K,?capacity?4486K,?committed?4864K,?reserved?1056768K

classspace????used?285K,?capacity?386K,?committed?512K,?reserved?1048576K

//當(dāng)注釋allocation3和4的這兩句代碼時规脸,GC的運(yùn)行日志如下:所以坯约,這樣就很好理解了上文中為何會在allocation3處發(fā)生了一次Minor?GC。

Heap

PSYoungGen??????total?6656K,?used?5061K?[0x00000000ff900000,0x0000000100000000,0x0000000100000000)

eden?space?6144K,82%?used?[0x00000000ff900000,0x00000000ffdf14d0,0x00000000fff00000)

from?space?512K,0%?used?[0x00000000fff80000,0x00000000fff80000,0x0000000100000000)

to???space?512K,0%?used?[0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)

ParOldGen???????total?13312K,?used?0K?[0x00000000fec00000,0x00000000ff900000,0x00000000ff900000)

object?space?13312K,0%?used?[0x00000000fec00000,0x00000000fec00000,0x00000000ff900000)

Metaspace???????used?2580K,?capacity?4486K,?committed?4864K,?reserved?1056768K

classspace????used?285K,?capacity?386K,?committed?512K,?reserved?1048576K

上述代碼運(yùn)行在新生代總空間為7M的虛擬機(jī)里莫鸭,當(dāng)分配到第三個對象時就發(fā)生了一次GC闹丐,相信大家對比上面的兩個GC日志就明白了為什么在會allocation3=new byte[2*_1MB]處發(fā)生GC。

大對象直接進(jìn)入老年代:虛擬機(jī)提供了一個-XX:PretenureSizeThreshold參數(shù)被因,令大于這個設(shè)置值的對象直接在老年代分配卿拴。這樣做的目的是避免在Eden區(qū)及兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制衫仑。

長期存活的對象將進(jìn)入老年代:既然虛擬機(jī)采用了分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時就必須能識別哪些對象應(yīng)放在新生代堕花,哪些對象應(yīng)放在老年代中文狱。為了做到這點(diǎn),虛擬機(jī)給每個對象定義了一個對象年齡計數(shù)器缘挽。如果對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活如贷,并且能被Survivor容納的話,將被移動到Survivor空間中到踏,并且對象年齡設(shè)為1杠袱。對象在Survivor區(qū)中每“熬過”一次Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲)窝稿,就將會被晉升到老年代中楣富。對象晉升老年代的年齡閾值,可以通過參數(shù)-XX:MaxTenuringThreshold設(shè)置伴榔。

動態(tài)對象年齡判定:為了能更好地適應(yīng)不同程序的內(nèi)存狀況纹蝴,虛擬機(jī)并不是永遠(yuǎn)地要求對象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半踪少,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代塘安,無須等到MaxTenuringThreshold中要求的年齡。

空間分配擔(dān)保:在發(fā)生Minor GC之前援奢,虛擬機(jī)會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間兼犯,如果這個條件成立,那么Minor GC可以確保是安全的集漾。如果不成立切黔,則虛擬機(jī)會查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許具篇,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小纬霞,如果大于,將嘗試著進(jìn)行一次Minor GC驱显,盡管這次Minor GC是有風(fēng)險的诗芜;如果小于,或者HandlePromotionFailure設(shè)置不允許冒險埃疫,那這時也要改為進(jìn)行一次Full GC伏恐。

這里有必要說一下Minor GC和Full GC:

新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動作,因?yàn)镴ava對象大多都具備朝生夕滅的特性熔恢,所以Minor GC非常頻繁脐湾,一般回收速度也比較快臭笆。

老年代GC(Major GC/Full GC):指發(fā)生在老年代的GC叙淌,出現(xiàn)了Major GC秤掌,經(jīng)常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程)鹰霍,Major GC的速度一般會比Minor GC 慢10倍以上闻鉴。

其實(shí)到了這里,第三章的內(nèi)容差不多已經(jīng)總結(jié)完全了茂洒,后面會繼續(xù)往下總結(jié)孟岛,等把相關(guān)章節(jié)的內(nèi)容整理完畢,再來對這個系列的文章進(jìn)行review進(jìn)而修改,如果覺得不錯督勺,請頂一下哈渠羞。

轉(zhuǎn)載請注明出處:http://blog.csdn.net/android_jiangjun/article/details/78125281

作者:GeneralAndroid

鏈接:www.reibang.com/p/bee8e30c8aea?

來源:簡書

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)智哀,非商業(yè)轉(zhuǎn)載請注明出處次询。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓷叫,隨后出現(xiàn)的幾起案子屯吊,更是在濱河造成了極大的恐慌,老刑警劉巖摹菠,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盒卸,死亡現(xiàn)場離奇詭異,居然都是意外死亡次氨,警方通過查閱死者的電腦和手機(jī)蔽介,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來煮寡,“玉大人屉佳,你說我怎么就攤上這事≈扪海” “怎么了武花?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杈帐。 經(jīng)常有香客問我体箕,道長,這世上最難降的妖魔是什么挑童? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任累铅,我火速辦了婚禮,結(jié)果婚禮上站叼,老公的妹妹穿的比我還像新娘娃兽。我一直安慰自己,他們只是感情好尽楔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布投储。 她就那樣靜靜地躺著第练,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玛荞。 梳的紋絲不亂的頭發(fā)上娇掏,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音勋眯,去河邊找鬼婴梧。 笑死,一個胖子當(dāng)著我的面吹牛客蹋,可吹牛的內(nèi)容都是我干的塞蹭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼讶坯,長吁一口氣:“原來是場噩夢啊……” “哼浮还!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起闽巩,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤钧舌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后涎跨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洼冻,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年隅很,在試婚紗的時候發(fā)現(xiàn)自己被綠了撞牢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡叔营,死狀恐怖屋彪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绒尊,我是刑警寧澤畜挥,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站婴谱,受9級特大地震影響蟹但,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谭羔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一华糖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘟裸,春花似錦客叉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卵慰。三九已至,卻和暖如春向族,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拴清,地道東北人猜年。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像妆偏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容