《深入理解Java虛擬機(jī)》學(xué)習(xí)筆記(三)(垃圾收集器及內(nèi)存分配策略)

前言

本文章部分引用自

垃圾收集器及內(nèi)存分配策略

判斷對(duì)象存活

判斷對(duì)象存活算法

兩種:引用計(jì)數(shù)算法和可達(dá)性分析算法

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

給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí)直撤,計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí)灿意,計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的让簿。

圖1 引用計(jì)數(shù)算法示意圖

可達(dá)性分析算法

通過(guò)一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn)叮称,從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱為引用鏈(Reference Chain)曹洽,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(用圖論的話來(lái)說(shuō),就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí)辽剧,則證明此對(duì)象是不可用的送淆。

  • GC Roots對(duì)象
    • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象。

      • 這主要指main方法中產(chǎn)生的儲(chǔ)存在JVM棧中的對(duì)對(duì)象的引用怕轿、GC會(huì)去找當(dāng)前stack區(qū)里還留有的main方法產(chǎn)生的引用偷崩。


        圖2 GC查找棧中留有main方法的引用
    • 方法區(qū)中類靜態(tài)屬性引用的對(duì)象。

      • 靜態(tài)方法和變量不產(chǎn)生實(shí)例撞羽,直接由類引用阐斜。Java的類
        由java.lang.ClassLoader類加載器加載,類的數(shù)據(jù)都不在邏輯堆诀紊,而是存在Method Area方法區(qū)谒出,現(xiàn)在叫Metaspace。類本身一旦被GC清除渡紫,他的所有靜態(tài)變量也就跟著被釋放了
    • 方法區(qū)中常量引用的對(duì)象到推。

    • 本地方法棧中JNI(即一般說(shuō)的Native方法)引用的對(duì)象考赛。

引用的分類

  • 強(qiáng)引用
    • 類似“Object obj=new Object()”這類的引用惕澎,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象
  • 軟引用
    • 描述一些還有用但并非必需的對(duì)象颜骤。 對(duì)于軟引用關(guān)聯(lián)著的對(duì)象唧喉,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收忍抽。 如果這次回收還沒(méi)有足夠的內(nèi)存八孝,才會(huì)拋出內(nèi)存溢出異常。
  • 弱引用
    • 用來(lái)描述非必需對(duì)象的鸠项,但是它的強(qiáng)度比軟引用更弱一些干跛,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前。 當(dāng)垃圾收集器工作時(shí)祟绊,無(wú)論當(dāng)前內(nèi)存是否足夠楼入,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象哥捕。
  • 虛引用
    • 也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系嘉熊。 一個(gè)對(duì)象是否有虛引用的存在遥赚,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象實(shí)例阐肤。
圖3 引用的分類

宣告一個(gè)對(duì)象死亡的過(guò)程

要真正宣告一個(gè)對(duì)象死亡凫佛,至少要經(jīng)歷兩次標(biāo)記過(guò)程:

  • 如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與GC Roots相連接的引用鏈,那它將會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選孕惜,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法愧薛。
    • 當(dāng)對(duì)象沒(méi)有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過(guò),虛擬機(jī)將這兩種情況都視為“沒(méi)有必要執(zhí)行”偏塞。
    • 如果這個(gè)對(duì)象被判定為有必要執(zhí)行finalize()方法菜职,那么這個(gè)對(duì)象將會(huì)放置在一個(gè)叫做F-Queue的隊(duì)列之中,并在稍后由一個(gè)由虛擬機(jī)自動(dòng)建立的碘箍、 低優(yōu)先級(jí)的Finalizer線程去執(zhí)行它。
      • 這里所謂的“執(zhí)行”是指虛擬機(jī)會(huì)觸發(fā)這個(gè)方法鲸郊,但并不承諾會(huì)等待它運(yùn)行結(jié)束丰榴,這樣做的原因是,如果一個(gè)對(duì)象在finalize()方法中執(zhí)行緩慢秆撮,或者發(fā)生了死循環(huán)(更極端的情況)四濒,將很可能會(huì)導(dǎo)致F-Queue隊(duì)列中其他對(duì)象永久處于等待,甚至導(dǎo)致整個(gè)內(nèi)存回收系統(tǒng)崩潰职辨。
  • finalize()方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì)盗蟆,稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記
    • 如果對(duì)象要在finalize()中成功拯救自己——只要重新與引用鏈
      上的任何一個(gè)對(duì)象建立關(guān)聯(lián)
      即可,譬如把自己(this關(guān)鍵字)賦值給某個(gè)類變量或者對(duì)象的成員變量舒裤,那在第二次標(biāo)記時(shí)它將被移除出“即將回收”的集合喳资;
    • 如果對(duì)象這時(shí)候還沒(méi)有逃脫,那基本上它就真的被回收了腾供。
圖4 宣告一個(gè)對(duì)象死亡的過(guò)程

回收方法區(qū)

永久代(方法區(qū))的垃圾收集主要回收兩部分內(nèi)容:廢棄常量和無(wú)用的類

  • 廢棄常量
    • 假如一個(gè)字符串“abc”已經(jīng)進(jìn)入了常量池中仆邓,但是當(dāng)前系統(tǒng)沒(méi)有任何一個(gè)String對(duì)象是叫做“abc”的,換句話說(shuō)伴鳖,就是沒(méi)有任何String對(duì)象引用常量池中的“abc”常量节值,也沒(méi)有其他地方引用了這個(gè)字面量,如果這時(shí)發(fā)生內(nèi)存回收榜聂,而且必要的話搞疗,這個(gè)“abc”常量就會(huì)被系統(tǒng)清理出常量池。
  • 無(wú)用的類:同時(shí)滿足下面3個(gè)條件的類(實(shí)例须肆、類加載器被回收匿乃,java.lang.Class對(duì)象沒(méi)有被引用)
    • 該類所有的實(shí)例都已經(jīng)被回收脐往,也就是Java堆中不存在該類的任何實(shí)例。
    • 加載該類的ClassLoader已經(jīng)被回收扳埂。
    • 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用业簿,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法。

垃圾收集算法

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

算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象阳懂,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象(標(biāo)記過(guò)程在上文宣告一個(gè)對(duì)象死亡過(guò)程中提及

  • 缺點(diǎn)
    • 效率問(wèn)題梅尤,標(biāo)記和清除兩個(gè)過(guò)程的效率都不高(回收后空間碎片過(guò)多,再次回收(即可達(dá)性分析時(shí))有時(shí)需要遍歷整個(gè)內(nèi)存區(qū)域
    • 空間問(wèn)題岩调,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片巷燥,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過(guò)程中需要分配較大對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作号枕。
圖5 標(biāo)記-清除算法

復(fù)制算法(新生代算法

將可用內(nèi)存按容量劃分為大小相等的兩塊缰揪,每次只使用其中的一塊。 當(dāng)這一塊的內(nèi)存用完了葱淳,就將還存活著的對(duì)象復(fù)制到另外一塊上面钝腺,然后再把已使用過(guò)的內(nèi)存空間一次清理掉。

  • 優(yōu)點(diǎn)
    • 每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收赞厕,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況艳狐,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可皿桑,實(shí)現(xiàn)簡(jiǎn)單毫目,運(yùn)行高效
  • 缺點(diǎn)
    • 代價(jià)是將內(nèi)存縮小為了原來(lái)的一半,未免太高了一點(diǎn)诲侮。
    • 解決方法
        新生代中的對(duì)象98%是“朝生夕死”的镀虐,所以并不需要按照1:1的比例來(lái)劃分內(nèi)存空間,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間沟绪,每次使用Eden和其中一塊Survivor刮便。
        當(dāng)回收時(shí),將Eden和Survivor中還存活著的對(duì)象一次性地復(fù)制到另外一塊Survivor空間上近零,最后清理掉Eden和剛才用過(guò)的Survivor空間诺核。
  • 當(dāng)Survivor空間不夠用時(shí),需要依賴其他內(nèi)存(老年代)進(jìn)行分配擔(dān)保(Handle Promotion)
    圖6 復(fù)制算法示意圖1
圖7 復(fù)制算法示意圖2

標(biāo)記-整理算法(老年代算法

標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣久信,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng)漓摩,然后直接清理掉端邊界以外的內(nèi)存

圖8 標(biāo)記-整理算法示意圖

分代收集算法

根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊裙士。 一般是把Java堆
分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?在新生代中管毙,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去腿椎,只有少量存活桌硫,那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集啃炸。 而老年代中因?yàn)閷?duì)象存活率高铆隘、 沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記—清理”或者“標(biāo)記—整理”算法來(lái)進(jìn)行回收南用。

HotSpot的算法實(shí)現(xiàn)

枚舉根節(jié)點(diǎn)

  • 可達(dá)性分析的缺點(diǎn)
    • 從GC Roots節(jié)點(diǎn)找引用鏈這個(gè)操作為例膀钠,可作為GC Roots的節(jié)點(diǎn)主要在全局性的引用(例如常量或類靜態(tài)屬性)與執(zhí)行上下文(例如棧幀中的本地變量表)中,現(xiàn)在很多應(yīng)用僅僅方法區(qū)就有數(shù)百兆裹虫,如果要逐個(gè)檢查這里面的引用肿嘲,那么必然會(huì)消耗很多時(shí)間。
    • 可達(dá)性分析對(duì)執(zhí)行時(shí)間的敏感還體現(xiàn)在GC停頓上筑公,因?yàn)檫@項(xiàng)分析工作必須在一個(gè)能確保一致性的快照中進(jìn)行——這里“一致性”的意思是指在整個(gè)分析期間整個(gè)執(zhí)行系統(tǒng)看起來(lái)就像被凍結(jié)在某個(gè)時(shí)間點(diǎn)上雳窟,不可以出現(xiàn)分析過(guò)程中對(duì)象引用關(guān)系還在不斷變化的情況,該點(diǎn)不滿足的話分析結(jié)果準(zhǔn)確性就無(wú)法得到保證匣屡。 這點(diǎn)是導(dǎo)致GC進(jìn)行時(shí)必須停頓所有Java執(zhí)行線程(Sun將這件事情稱為“Stop The World”)的其中一個(gè)重要原因封救,即使是在號(hào)稱(幾乎)不會(huì)發(fā)生停頓的CMS收集器中,枚舉根節(jié)點(diǎn)時(shí)也是必須要停頓的捣作。

由于目前的主流Java虛擬機(jī)使用的都是準(zhǔn)確式GC兴泥,所以當(dāng)執(zhí)行系統(tǒng)停頓下來(lái)后,并不需要一個(gè)不漏地檢查完所有執(zhí)行上下文和全局的引用位置虾宇,虛擬機(jī)應(yīng)當(dāng)是有辦法直接得知哪些地方存放著對(duì)象引用搓彻。 在HotSpot的實(shí)現(xiàn)中,是使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)達(dá)到這個(gè)目的的嘱朽,在類加載完成的時(shí)候旭贬,HotSpot就把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來(lái),在JIT編譯過(guò)程中搪泳,也會(huì)在特定的位置記錄下棧和寄存器中哪些位置是引用稀轨。 這樣,GC在掃描時(shí)就可以直接得知這些信息了岸军。

  • 判斷對(duì)象引用
    • 類加載時(shí)奋刽,使用OopMap的數(shù)據(jù)結(jié)構(gòu)
    • JIT編譯時(shí)特定記錄

安全點(diǎn)

在OopMap的協(xié)助下,HotSpot可以快速且準(zhǔn)確地完成GC Roots枚舉艰赞,但可能導(dǎo)致引用關(guān)系變化佣谐,或者說(shuō)OopMap內(nèi)容變化的指令非常多,如果為每一條指令都生成對(duì)應(yīng)的OopMap方妖,那將會(huì)需要大量的額外空間狭魂,這樣GC的空間成本將會(huì)變得很高。(選取的安全點(diǎn)引用關(guān)系變化不大,且安全點(diǎn)的個(gè)數(shù)較為適宜)
實(shí)際上雌澄,HotSpot也的確沒(méi)有為每條指令都生成OopMap斋泄,前面已經(jīng)提到,只是在“特定的位置”記錄了這些信息镐牺,這些位置稱為安全點(diǎn)(Safepoint)炫掐,即程序執(zhí)行時(shí)并非在所有地方都能停頓下來(lái)開(kāi)始GC,只有在到達(dá)安全點(diǎn)時(shí)才能暫停睬涧。

在GC發(fā)生時(shí)讓所有線程(這里不包括執(zhí)行JNI調(diào)用的線程)都“跑”到最近的安全點(diǎn)上再停頓下來(lái)募胃,有兩種方法:搶先式中斷和主動(dòng)式中斷

  • 搶先式中斷
    • 不需要線程的執(zhí)行代碼主動(dòng)去配合,在GC發(fā)生時(shí)宙地,首先把所有線程全部中斷摔认,如果發(fā)現(xiàn)有線程中斷的地方不在安全點(diǎn)上,就恢復(fù)線程宅粥,讓它“跑”到安全點(diǎn)上参袱。 現(xiàn)在幾乎沒(méi)有虛擬機(jī)實(shí)現(xiàn)采用搶先式中斷來(lái)暫停線程從而響應(yīng)GC事件
  • 主動(dòng)式中斷
    • 當(dāng)GC需要中斷線程的時(shí)候秽梅,不直接對(duì)線程操作抹蚀,僅僅簡(jiǎn)單地設(shè)置一個(gè)標(biāo)志,各個(gè)線程執(zhí)行時(shí)主動(dòng)去輪詢這個(gè)標(biāo)志企垦,發(fā)現(xiàn)中斷標(biāo)志為真時(shí)就自己中斷掛起环壤。輪詢標(biāo)志的地方和安全點(diǎn)是重合的,另外再加上創(chuàng)建對(duì)象需要分配內(nèi)存的地方钞诡。
  • 兩者的區(qū)別在于郑现,搶先式中斷是無(wú)論如何都進(jìn)行中斷,而主動(dòng)式中斷則是線程執(zhí)行輪詢標(biāo)志查看是否中斷

安全區(qū)域

使用Safepoint似乎已經(jīng)完美地解決了如何進(jìn)入GC的問(wèn)題荧降,但實(shí)際情況卻并不一定接箫。Safepoint機(jī)制保證了程序執(zhí)行時(shí),在不太長(zhǎng)的時(shí)間內(nèi)就會(huì)遇到可進(jìn)入GC的Safepoint朵诫。 但是辛友,程序“不執(zhí)行”的時(shí)候呢?所謂的程序不執(zhí)行就是沒(méi)有分配CPU時(shí)間剪返,典型的例子就是線程處于Sleep狀態(tài)或者Blocked狀態(tài)废累,這時(shí)候線程無(wú)法響應(yīng)JVM的中斷請(qǐng)求,“走”到安全的地方去中斷掛起脱盲,JVM也顯然不太可能等待線程重新被分配CPU時(shí)間邑滨。 對(duì)于這種情況,就需要安全區(qū)域(Safe Region)來(lái)解決宾毒。

安全區(qū)域是指在一段代碼片段之中驼修,引用關(guān)系不會(huì)發(fā)生變化殿遂。 在這個(gè)區(qū)域中的任意地方開(kāi)始GC都是安全的诈铛。

在線程執(zhí)行到Safe Region中的代碼時(shí)乙各,首先標(biāo)識(shí)自己已經(jīng)進(jìn)入了Safe Region,那樣幢竹,當(dāng)在這段時(shí)間里JVM要發(fā)起GC時(shí)耳峦,就不用管標(biāo)識(shí)自己為Safe Region狀態(tài)的線程了。 在線程要離開(kāi)Safe Region時(shí)焕毫,它要檢查系統(tǒng)是否已經(jīng)完成了根節(jié)點(diǎn)枚舉(或者是整個(gè)GC過(guò)程)蹲坷,如果完成了,那線程就繼續(xù)執(zhí)行邑飒,否則它就必須等待直到收到可以安全離開(kāi)Safe Region的信號(hào)為止循签。

垃圾收集器

HotSpot虛擬機(jī)包含的垃圾收集器包括:Serial收集器、ParNew收集器疙咸、Parallel Scavenge收集器县匠、Serial Old收集器、Parallel Old收集器撒轮、CMS收集器乞旦、G1收集器

圖9 HotSpot虛擬機(jī)所包含的所有垃圾收集器

Serial收集器

是一個(gè)單線程的收集器,但它的“單線程”的意義并不僅僅說(shuō)明它只會(huì)使用一個(gè)CPU或一條收集線程去完成垃圾收集工作题山,更重要的是在它進(jìn)行垃圾收集時(shí)兰粉,必須暫停其他所有的工作線程,直到它收集結(jié)束顶瞳。

它依然是虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器

圖10 Serial/Serial Old收集器運(yùn)行示意圖

ParNew收集器

  • ParNew收集器其實(shí)就是Serial收集器的多線程版本
  • 是許多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器
  • 除了Serial收集器外玖姑,目前只有它能與CMS收集器配合工作。
  • 并發(fā)和并行
    • 并行(Parallel):指多條垃圾收集線程并行工作慨菱,但此時(shí)用戶線程仍然處于等待狀態(tài)焰络。
    • 并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的,可能會(huì)交替執(zhí)行)抡柿,用戶程序在繼續(xù)運(yùn)行舔琅,而垃圾收集程序運(yùn)行于另一個(gè)CPU上。
圖11 ParNew/Serial Old收集器運(yùn)行示意圖

Parallel Scavenge收集器

Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughput)洲劣。

  • 吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值备蚓,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間),虛擬機(jī)總共運(yùn)行了100分鐘囱稽,其中垃圾收集花掉1分鐘郊尝,那吞吐量就是99%。

Parallel Scavenge收集器提供了兩個(gè)參數(shù)用于精確控制吞吐量

  • 控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMillis參數(shù)
  • 直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)

Serial Old收集器

Serial Old是Serial收集器的老年代版本战惊,它同樣是一個(gè)單線程收集器流昏,使用“標(biāo)記-整理”算法。(可查看圖10)

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法况凉。

圖11 Parallel Scavenge/Parallel Old收集器運(yùn)行示意圖

CMS收集器

是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器谚鄙。

CMS收集器是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的,分為四個(gè)步驟

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

    • 仍然需要“Stop The World”刁绒。
    • 僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象闷营,速度很快(第一層節(jié)點(diǎn))
  • 并發(fā)標(biāo)記(CMS concurrent mark)

    • 進(jìn)行GC RootsTracing的過(guò)程
  • 重新標(biāo)記(CMS remark)

    • 仍然需要“Stop The World”。
    • 為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄知市,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段稍長(zhǎng)一些傻盟,但遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短。
  • 并發(fā)清除(CMS concurrent sweep)

  • 缺點(diǎn)

    • CMS收集器對(duì)CPU資源非常敏感嫂丙。
    • CMS收集器無(wú)法處理浮動(dòng)垃圾(Floating Garbage)
      • 由于CMS并發(fā)清理階段用戶線程還在運(yùn)行著娘赴,伴隨程序運(yùn)行自然就還會(huì)有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過(guò)程之后跟啤,CMS無(wú)法在當(dāng)次收集中處理掉它們诽表,只好留待下一次GC時(shí)再清理掉。 這一部分垃圾就稱為“浮動(dòng)垃圾”腥光。
    • 收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生关顷。
      • 空間碎片過(guò)多時(shí),將會(huì)給大對(duì)象分配帶來(lái)很大麻煩武福,往往會(huì)出現(xiàn)老年代還有很大空間剩余议双,但是無(wú)法找到足夠大的連續(xù)空間來(lái)分配當(dāng)前對(duì)象,不得不提前觸發(fā)一次FullGC捉片。
圖12 Concurrent Mark Sweep收集器運(yùn)行示意圖

G1收集器

是一款面向服務(wù)端應(yīng)用的垃圾收集器平痰。

  • 特點(diǎn)
    • 并行與并發(fā)
      • G1能充分利用多CPU、 多核環(huán)境下的硬件優(yōu)勢(shì)伍纫,使用多個(gè)CPU(CPU或者CPU核心)來(lái)縮短Stop-The-World停頓的時(shí)間宗雇,部分其他收集器原本需要停頓Java線程執(zhí)行的GC動(dòng)作,G1收集器仍然可以通過(guò)并發(fā)的方式讓Java程序繼續(xù)執(zhí)行莹规。
    • 分代收集
      • 與其他收集器一樣赔蒲,分代概念在G1中依然得以保留。 雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個(gè)GC堆良漱,但它能夠采用不同的方式去處理新創(chuàng)建的對(duì)象和已經(jīng)存活了一段時(shí)間舞虱、 熬過(guò)多次GC的舊對(duì)象以獲取更好的收集效果。
    • 空間整合
      • 與CMS的“標(biāo)記—清理”算法不同母市,G1從整體來(lái)看是基于“標(biāo)記—整理”算法實(shí)現(xiàn)的收集器矾兜,從局部(兩個(gè)Region之間)上來(lái)看是基于“復(fù)制”算法實(shí)現(xiàn)的,但無(wú)論如何患久,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片椅寺,收集后能提供規(guī)整的可用內(nèi)存浑槽。 這種特性有利于程序長(zhǎng)時(shí)間運(yùn)行,分配大對(duì)象時(shí)不會(huì)因?yàn)闊o(wú)法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC返帕。
    • 可預(yù)測(cè)的停頓
      • 這是G1相對(duì)于CMS的另一大優(yōu)勢(shì)桐玻,降低停頓時(shí)間是G1和CMS共同的關(guān)注點(diǎn),但G1除了追求低停頓外溉旋,還能建立可預(yù)測(cè)的停頓時(shí)間模型畸冲,能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi)嫉髓,消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒观腊,這幾乎已經(jīng)是實(shí)時(shí)Java(RTSJ)的垃圾收集器的特征了

使用G1收集器時(shí),Java堆的內(nèi)存布局就與其他收集器有很大差別算行,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)梧油,雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了州邢,它們都是一部分Region(不需要連續(xù))的集合儡陨。

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名稱的來(lái)由)。 這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式裙秋,保證了G1收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率琅拌。

在G1收集器中,Region之間的對(duì)象引用以及其他收集器中的新生代與老年代之間的對(duì)象引用摘刑,虛擬機(jī)都是使用Remembered Set來(lái)避免全堆掃描的进宝。 G1中每個(gè)Region都有一個(gè)與之對(duì)應(yīng)的Remembered Set,虛擬機(jī)發(fā)現(xiàn)程序在對(duì)Reference類型的數(shù)據(jù)進(jìn)行寫(xiě)操作時(shí)枷恕,會(huì)產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷寫(xiě)操作党晋,檢查Reference引用的對(duì)象是否處于不同的Region之中(在分代的例子中就是檢查是否老年代中的對(duì)象引用了新生代中的對(duì)象),如果是徐块,便通過(guò)CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬的Region的Remembered Set之中未玻。 當(dāng)進(jìn)行內(nèi)存回收時(shí),在GC根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set即可保證不對(duì)全堆掃描也不會(huì)有遺漏蛹锰。

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

  • 初始標(biāo)記(Initial Marking)
    • 僅僅只是標(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)
    • 從GC Root開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析余爆,找出存活的對(duì)象,這階段耗時(shí)較長(zhǎng)夸盟,但可與用戶程序并發(fā)執(zhí)行蛾方。
  • 最終標(biāo)記(Final Marking)
    • 為了修正在并發(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í)間來(lái)制定回收計(jì)劃
圖12 G1收集器運(yùn)行示意圖

內(nèi)存分配與回收策略

對(duì)象主要分配在新生代的Eden區(qū)上,如果啟動(dòng)了本地線程分配緩沖庶溶,將按線程優(yōu)先在TLAB上分配煮纵。(預(yù)先在TLAB上為每個(gè)線程分配一定大小的內(nèi)存),少數(shù)情況下也可能會(huì)直接分配在老年代中

  • 對(duì)象優(yōu)先在Eden分配

    • 當(dāng)Eden區(qū)沒(méi)有足夠空間進(jìn)行分配時(shí)偏螺,虛擬機(jī)將發(fā)起一次Minor GC行疏。
      • 新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)镴ava對(duì)象大多都具備朝生夕滅的特性套像,所以Minor GC非常頻繁酿联,一般回收速度也比較快。
      • 老年代GC(Major GC/Full GC):指發(fā)生在老年代的GC凉夯,出現(xiàn)了Major GC货葬,經(jīng)常會(huì)伴隨至少一次的Minor GC(但非絕對(duì)的,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過(guò)程)劲够。 Major GC的速度一般會(huì)比Minor GC慢10倍以上震桶。
  • 大對(duì)象直接進(jìn)入老年代
    所謂的大對(duì)象是指,需要大量連續(xù)內(nèi)存空間的Java對(duì)象征绎,最典型的大對(duì)象就是那種很長(zhǎng)的字符串以及數(shù)組

  • 長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
    虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(Age)計(jì)數(shù)器蹲姐。 如果對(duì)象在Eden出生并經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納的話人柿,將被移動(dòng)到Survivor空間中柴墩,并且對(duì)象年齡設(shè)為1。 對(duì)象在Survivor區(qū)中每“熬過(guò)”一次Minor GC凫岖,年齡就增加1歲江咳,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲),就將會(huì)被晉升到老年代中哥放。

動(dòng)態(tài)對(duì)象年齡判定

如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半歼指,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代

空間分配擔(dān)保

只要老年代的連續(xù)空間大于新生代對(duì)象總大小或者歷次晉升的平均大小就會(huì)進(jìn)行Minor GC爹土,否則將進(jìn)行Full GC。

問(wèn)題

  • 為什么程序要跑到安全點(diǎn)時(shí)停下來(lái)踩身?
    • 不設(shè)置安全點(diǎn)胀茵,而讓每一條指令都產(chǎn)生Oop(Ordinary Object Pointer)會(huì)需要大量的額外空間,增大GC的空間成本挟阻。設(shè)置了合適的安全點(diǎn)琼娘,有助于虛擬機(jī)得知對(duì)象引用所在的地方,因此有利于GC對(duì)“即將回收”的對(duì)象進(jìn)行掃描附鸽。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末脱拼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拒炎,更是在濱河造成了極大的恐慌挪拟,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件击你,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谎柄,警方通過(guò)查閱死者的電腦和手機(jī)丁侄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)朝巫,“玉大人鸿摇,你說(shuō)我怎么就攤上這事∨常” “怎么了拙吉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)揪荣。 經(jīng)常有香客問(wèn)我筷黔,道長(zhǎng),這世上最難降的妖魔是什么仗颈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任佛舱,我火速辦了婚禮,結(jié)果婚禮上挨决,老公的妹妹穿的比我還像新娘请祖。我一直安慰自己,他們只是感情好脖祈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布肆捕。 她就那樣靜靜地躺著,像睡著了一般盖高。 火紅的嫁衣襯著肌膚如雪慎陵。 梳的紋絲不亂的頭發(fā)上掏秩,一...
    開(kāi)封第一講書(shū)人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音荆姆,去河邊找鬼蒙幻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛胆筒,可吹牛的內(nèi)容都是我干的邮破。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼仆救,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼抒和!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起彤蔽,我...
    開(kāi)封第一講書(shū)人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤摧莽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后顿痪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體镊辕,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蚁袭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了征懈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揩悄,死狀恐怖卖哎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情删性,我是刑警寧澤亏娜,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蹬挺,受9級(jí)特大地震影響维贺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汗侵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一幸缕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晰韵,春花似錦发乔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至只恨,卻和暖如春译仗,著一層夾襖步出監(jiān)牢的瞬間抬虽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工纵菌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阐污,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓咱圆,卻偏偏與公主長(zhǎng)得像笛辟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子序苏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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