深入理解Java虛擬機(jī)3——垃圾回收

《深入理解Java虛擬機(jī)》第3章讀書筆記

本文介紹了如何判斷對(duì)象是否存活嘿歌,三種垃圾回收算法哪雕,分析比較了幾種垃圾收集器的特點(diǎn)。本文并非原創(chuàng)更振,是《深入理解Java虛擬機(jī)》第3章的整理炕桨、總結(jié)和補(bǔ)充。

對(duì)象已死肯腕?

垃圾收集器在對(duì)堆進(jìn)行回收前献宫,要先判斷哪些對(duì)象“存活”,哪些已經(jīng)“死去”实撒。

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

給對(duì)象中添加一個(gè)引用計(jì)數(shù)器姊途,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器就加1知态;當(dāng)引用失效時(shí)捷兰,計(jì)數(shù)器就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的负敏。

主流的Java虛擬機(jī)里面沒有選用引用計(jì)數(shù)算法來管理內(nèi)存贡茅。

優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,效率高其做。

缺點(diǎn):很難解決對(duì)象之間相互循環(huán)引用的問題友扰。

循環(huán)引用問題彤叉,如下代碼所示,

/**
 * 源代碼出自《深入理解Java虛擬機(jī)》P62-63
 * 循環(huán)引用
 **/
public class ReferenceCountingGC {

    public Object instance = null;

    private static final int _1MB = 1024 * 1024;

    /**
     * 這個(gè)成員屬性的唯一意義就是占點(diǎn)內(nèi)存村怪,以便能在GC日志中看清楚是否被回收過
     */
    private byte[] bigSize = new byte[2 * _1MB];

    /**
     * 運(yùn)行參數(shù)
     * -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M
     */
    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        // 假設(shè)在這行發(fā)生GC秽浇,objA和objB是否能被回收?
        System.gc();
    }
}

JVM參數(shù)設(shè)置了新生代為10MB甚负,運(yùn)行結(jié)果顯示柬焕,在第一次觸發(fā)GC時(shí),“5120K->576K(9216K)”回收了約4MB內(nèi)存梭域。意味著虛擬機(jī)并沒有因?yàn)閮蓚€(gè)對(duì)象相互引用就不回收它們斑举,這也側(cè)面說明了虛擬機(jī)并不是通過引用計(jì)數(shù)算法來判斷對(duì)象是否存活

image

可達(dá)性分析算法

通過一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索病涨,搜索走過的路徑稱為引用鏈(Reference Chain)富玷,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí)既穆,則證明此對(duì)象是不可用的赎懦。

如圖,object5幻工、object6励两、object7 為可回收對(duì)象

image

主流的Java虛擬機(jī)使用可達(dá)性分析算法

在Java語言中,GC Roots包括以下幾種:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
  • 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
  • 方法區(qū)中常量引用的對(duì)象
  • 本地方法棧中Native方法引用的對(duì)象

四種引用

  1. 強(qiáng)引用:代碼中普遍存在囊颅,垃圾收集器不會(huì)回收強(qiáng)引用的對(duì)象当悔。
  2. 軟引用:有用但非必需,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前踢代,會(huì)把這些對(duì)象列入回收范圍盲憎。
  3. 弱引用:非必需,無論當(dāng)前內(nèi)存是否足夠胳挎,下次垃圾回收都會(huì)回收掉這些對(duì)象饼疙。
  4. 虛引用:最弱的引用關(guān)系,是否有虛引用不對(duì)其生存時(shí)間構(gòu)成影響串远。

垃圾收集算法

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

最基礎(chǔ)的收集算法——“標(biāo)記-清除”(Mark-Sweep)算法宏多。一般用于老年代儿惫。

算法分為“標(biāo)記”和“清除”兩個(gè)階段:

  1. 標(biāo)記出需要回收的對(duì)象
  2. 清除被標(biāo)記的對(duì)象

缺點(diǎn):效率低澡罚,空間碎片化。

image

復(fù)制算法

為了解決效率問題肾请,出現(xiàn)了“復(fù)制”算法(Copying)留搔。它將內(nèi)存分為兩塊,每次只使用其中一塊铛铁,當(dāng)這一塊內(nèi)存用完了隔显,就將還存活的對(duì)象復(fù)制到另一塊上面却妨,然后再把已使用過的內(nèi)存空間一次清理掉。

缺點(diǎn):內(nèi)存縮小為原來的一半括眠。

image

JVM虛擬機(jī)在新生代使用這種收集算法彪标,并不是按照 1:1 的比例來劃分內(nèi)存空間。而是根據(jù)新生代中的對(duì)象98%是“朝生夕死”這一特點(diǎn)掷豺,將內(nèi)存分為一塊較大的 Eden(80%) 空間和兩塊較小的 Survivor(10%) 空間捞烟。每次只使用 Eden 和其中一塊 Survivor,當(dāng)回收時(shí)当船,將 Eden 和 Survivor 中還存活的對(duì)象题画,一次性地復(fù)制到另外一塊 Survivor 空間上,最后清理掉 Eden 和剛才用過的 Survivor 空間德频。

HotSpot默認(rèn)Eden和Survivor的大小比例為8:1苍息,這樣就只有10%的內(nèi)存被“浪費(fèi)”。當(dāng)出現(xiàn)超過10%的對(duì)象存活時(shí)壹置,就會(huì)使用老年代做分配擔(dān)保竞思,把Survivor空間放不下的對(duì)象,直接放入老年代蒸绩。

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

在Mark-Sweep算法的基礎(chǔ)上做了改良衙四,用于解決空間碎片化問題。標(biāo)記-整理(Mark-Compact)算法在標(biāo)記后不是簡(jiǎn)單做清除患亿,而是讓所有存活的對(duì)象都向一端移動(dòng)传蹈,然后清理掉端邊界以外的內(nèi)存。一般用于老年代步藕。

image

安全點(diǎn)和安全區(qū)域

安全點(diǎn)

在做可達(dá)性分析時(shí)惦界,需要保持分析期間整個(gè)系統(tǒng)不會(huì)發(fā)生變化,這就導(dǎo)致GC進(jìn)行時(shí)必須停頓所有Java執(zhí)行線程(Stop The World)咙冗,即使是在號(hào)稱(幾乎)不會(huì)發(fā)生停頓的CMS收集器中沾歪,枚舉根節(jié)點(diǎn)時(shí)也必須要停頓。

程序執(zhí)行時(shí)并非在所有地方都能停下來開始GC,只有在到達(dá)安全點(diǎn)(Safepoint)時(shí)才能暫停洞渔。Safepoint 的選定既不能太少以致于讓GC等待時(shí)間太長(zhǎng)失都,也不能過于頻繁以致于過分增大運(yùn)行時(shí)的負(fù)荷。所以狂窑,安全點(diǎn)的選定基本上是以程序“是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”為標(biāo)準(zhǔn)進(jìn)行選定的,例如方法調(diào)用桑腮,循環(huán)跳轉(zhuǎn)泉哈,異常跳轉(zhuǎn)等。

如何在GC發(fā)生時(shí)讓線程都跑到最近的安全點(diǎn)再停頓下來?

  • 搶先試中斷:先把所有線程中斷丛晦,發(fā)現(xiàn)不在安全點(diǎn)的線程恢復(fù)線程奕纫,讓它跑到安全點(diǎn)。
  • 主動(dòng)式中斷:設(shè)置一個(gè)不可讀的內(nèi)存位置作為中斷標(biāo)志烫沙,標(biāo)志與安全點(diǎn)重合匹层,當(dāng)線程執(zhí)行到這個(gè)標(biāo)志時(shí)自己中斷掛起。

安全區(qū)域

安全區(qū)域(Safe Region)是指在一段代碼片段中锌蓄,引用關(guān)系不會(huì)發(fā)生變化又固。在這個(gè)區(qū)域的任何地方開始GC都是安全的。典型的安全區(qū)域比如線程處于Sleep狀態(tài)或者Blocked狀態(tài)煤率。

在線程執(zhí)行到Safe Region中的代碼時(shí)仰冠,首先標(biāo)識(shí)自己已經(jīng)進(jìn)入了Safe Region。當(dāng)要發(fā)起GC時(shí)蝶糯,就不用管標(biāo)識(shí)為Safe Region狀態(tài)的線程了洋只。當(dāng)線程要離開Safe Region時(shí),要檢查是否處于GC狀態(tài)昼捍,如果是识虚,就要繼續(xù)等待,直到收到可以安全離開Safe Region的信號(hào)為止妒茬。

垃圾收集器

并行與并發(fā)

  • 并行(Parallel):指多條垃圾收集線程并行工作担锤,但此時(shí)用戶線程仍然處于等待狀態(tài)。
  • 并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的乍钻,可能會(huì)交替執(zhí)行)肛循,用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個(gè)CPU上银择。

吞吐量

吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值多糠,即

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

假設(shè)虛擬機(jī)總共運(yùn)行了100分鐘浩考,其中垃圾收集花掉1分鐘夹孔,那吞吐量就是99%。

Minor GC 和 Full 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的策略選擇過程)佑菩。Major GC的速度一般會(huì)比Minor GC慢10倍以上。

HotSpot虛擬機(jī)的垃圾收集器對(duì)比

image
收集器 串行裁赠、并行殿漠、并發(fā) 新生代、老生代 算法 目標(biāo) 使用場(chǎng)景
Serial 單線程佩捞,串行 新生代 復(fù)制算法 響應(yīng)速度優(yōu)先 單CPU環(huán)境下的Client模式
Serial Old 單線程绞幌,串行 老年代 標(biāo)記-整理 響應(yīng)速度優(yōu)先 單CPU環(huán)境下的Client模式、CMS的后
ParNew 多線程一忱,并行 新生代 復(fù)制算法 響應(yīng)速度優(yōu)先 多CPU環(huán)境時(shí)在Server模式下與CMS配合
Parallel Scanvenge 多線程莲蜘,并行 新生代 復(fù)制算法 吞吐量?jī)?yōu)先 在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)
Parallel Old 多線程,并行 老年代 標(biāo)記-整理 吞吐量?jī)?yōu)先 在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)
CMS 并發(fā) 老年代 標(biāo)記-清除 響應(yīng)速度優(yōu)先 集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)服務(wù)端上的Java應(yīng)用
G1 并發(fā) both 復(fù)制算法+標(biāo)記-整理 響應(yīng)速度優(yōu)先 面向服務(wù)端應(yīng)用帘营,將來替換CMS

ParNew 收集器

Serial收集器的多線程版本票渠,Service模式下的首選新生代收集器,除了Serial收集器外芬迄,目前只有它能與CMS收集器配合工作问顷。

ParNew 收集器是使用 -XX:+UseConcMarkSweepGC 選項(xiàng)后的默認(rèn)新生代收集器,也可以使用 -XX:+UseParNewGC 選項(xiàng)來強(qiáng)制指定它禀梳。

運(yùn)行示意圖如下:

image

CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器杜窄。基于Mark-Sweep算法算途。運(yùn)行過程分為四個(gè)部分:

  • 初始標(biāo)記
  • 并發(fā)標(biāo)記
  • 重新標(biāo)記
  • 并發(fā)清除

其中塞耕,初始標(biāo)記和重新標(biāo)記仍然需要 Stop The World。初始標(biāo)記只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對(duì)象嘴瓤,速度很快扫外。并發(fā)標(biāo)記就是進(jìn)行 GC Roots Tracing 的過程,而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄廓脆,這個(gè)階段的停頓時(shí)間比初始標(biāo)記稍長(zhǎng)一些畏浆,但遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短。

由于整個(gè)過程中耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清楚過程收集器線程都可以與用戶線程一起工作狞贱,所以刻获,從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的瞎嬉。

運(yùn)行示意圖如下:

image

CMS 收集器有如下3個(gè)缺點(diǎn):

  1. 對(duì)CPU資源非常敏感

    因?yàn)椴l(fā)階段需要占用一個(gè)用戶線程蝎毡,如果CPU小于4個(gè),則會(huì)導(dǎo)致用戶程序的執(zhí)行速度下降大于25%氧枣,如果只有2個(gè)CPU沐兵,用戶程序執(zhí)行速度則會(huì)下降50%,這是讓人無法接收的便监。一般來說使用CMS收集器的服務(wù)器配置至少需要4個(gè)CPU扎谎。

  2. 無法處理浮動(dòng)垃圾

    在并發(fā)清理過程中產(chǎn)生的垃圾稱為“浮動(dòng)垃圾”碳想。這些垃圾只能等待下次垃圾回收。因此毁靶,CMS 收集器不能像其他收集器那樣等到老年代幾乎被完全填滿了再進(jìn)行收集胧奔,需要預(yù)留一部分空間提供并發(fā)收集時(shí)的程序運(yùn)作使用。

  3. 內(nèi)存空間碎片化

    CMS 收集器是基于Mark-Sweep算法预吆,這個(gè)算法會(huì)產(chǎn)生內(nèi)存空間碎片龙填。CMS 收集器提供了一個(gè) -XX:+UseCMSCompactAtFullCollection 開關(guān)參數(shù)(默認(rèn)為開啟),用于在CMS收集器頂不住要進(jìn)行Full GC時(shí)開啟內(nèi)存碎片的合并整理過程拐叉。內(nèi)存整理的過程是無法并發(fā)執(zhí)行的岩遗,空間碎片問題沒有了,但停頓時(shí)間不得不變長(zhǎng)凤瘦。

使用多個(gè)收集器配置宿礁,JVM會(huì)怎么處理?

如過同時(shí)使用了四個(gè)組合配置蔬芥,這是時(shí)候就會(huì)報(bào)錯(cuò)

image
image

但是比如 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 這兩個(gè)配置同時(shí)存在就不會(huì)報(bào)錯(cuò)窘拯。

翻看源碼可知

image

有些配置項(xiàng)是可以并存的。

其實(shí)坝茎,在使用 UseConcMarkSweepGC 配置的時(shí)候涤姊,虛擬機(jī)默認(rèn)開啟了 UseParNewGC

image

所以在配置JVM時(shí),我們盡量顯式配置嗤放。比如要啟用 ParNew + CMS 組合可以配置為

-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC

理解GC日志

image

《深入理解Java虛擬機(jī)》閱讀筆記系列

深入理解Java虛擬機(jī)1——內(nèi)存區(qū)域

深入理解Java虛擬機(jī)2——對(duì)象探秘

深入理解Java虛擬機(jī)3——垃圾回收

本文首發(fā)于我的個(gè)人博客 https://chaohang.top

作者 張小超

公眾號(hào)【超超不會(huì)飛】

轉(zhuǎn)載請(qǐng)注明出處

歡迎關(guān)注我的微信公眾號(hào) 【超超不會(huì)飛】思喊,獲取第一時(shí)間的更新。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末次酌,一起剝皮案震驚了整個(gè)濱河市恨课,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岳服,老刑警劉巖剂公,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吊宋,居然都是意外死亡纲辽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門璃搜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拖吼,“玉大人,你說我怎么就攤上這事这吻〉醯担” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵唾糯,是天一觀的道長(zhǎng)怠硼。 經(jīng)常有香客問我鬼贱,道長(zhǎng),這世上最難降的妖魔是什么香璃? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任这难,我火速辦了婚禮,結(jié)果婚禮上增显,老公的妹妹穿的比我還像新娘。我一直安慰自己脐帝,他們只是感情好同云,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堵腹,像睡著了一般炸站。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疚顷,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天旱易,我揣著相機(jī)與錄音,去河邊找鬼腿堤。 笑死阀坏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笆檀。 我是一名探鬼主播忌堂,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼酗洒!你這毒婦竟也來了士修?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤樱衷,失蹤者是張志新(化名)和其女友劉穎棋嘲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矩桂,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沸移,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侄榴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阔籽。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖牲蜀,靈堂內(nèi)的尸體忽然破棺而出笆制,到底是詐尸還是另有隱情,我是刑警寧澤涣达,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布在辆,位于F島的核電站证薇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏匆篓。R本人自食惡果不足惜浑度,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸦概。 院中可真熱鬧箩张,春花似錦、人聲如沸窗市。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咨察。三九已至论熙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摄狱,已是汗流浹背脓诡。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留媒役,地道東北人祝谚。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像酣衷,于是被迫代替她去往敵國和親踊跟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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