一、為什么要進(jìn)行垃圾回收
隨著程序的運(yùn)行,內(nèi)存中存在的實(shí)例對(duì)象栏饮、變量等信息占據(jù)的內(nèi)存越來(lái)越多,其中有很多對(duì)象再也用不到磷仰,這些用不到的對(duì)象就被稱之為垃圾袍嬉,如果不及時(shí)進(jìn)行垃圾回收,必然會(huì)帶來(lái)程序性能的下降灶平,甚至?xí)驗(yàn)榭捎脙?nèi)存不足造成一些不必要的系統(tǒng)異常伺通。
垃圾回收機(jī)制主要是對(duì) JVM 中堆內(nèi)存進(jìn)行管理,如果對(duì) JVM 相關(guān)的概念還不了解逢享,可以看一看《JVM 從入門到出門》這篇文章罐监。
二、如何判定對(duì)象是否為垃圾
1瞒爬、引用計(jì)數(shù)法
給對(duì)象添加一引用計(jì)數(shù)器弓柱,被引用一次計(jì)數(shù)器值就加 1;當(dāng)引用失效時(shí)疮鲫,計(jì)數(shù)器值就減 1吆你;計(jì)數(shù)器為 0 時(shí),對(duì)象就是垃圾俊犯。
優(yōu)點(diǎn)是執(zhí)行效率高妇多,缺點(diǎn)是無(wú)法解決對(duì)象之間相互循環(huán)引用的問(wèn)題鲸郊。
2啊研、可達(dá)性分析算法
以 GC Roots 為起始點(diǎn)進(jìn)行搜索,判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá)屁商,可達(dá)的對(duì)象都是存活的绢彤,不可達(dá)的對(duì)象可被回收七问。GC Roots 一般包含以下內(nèi)容:
- 虛擬機(jī)棧中局部變量表中引用的對(duì)象
- 本地方法棧中 JNI 中引用的對(duì)象
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中的常量引用的對(duì)象
對(duì)象死亡(被回收)前的最后一次掙扎:
即使在可達(dá)性分析算法中不可達(dá)的對(duì)象,也并非是“必死不可”茫舶,這時(shí)候它們暫時(shí)處于“緩刑”階段械巡,要真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷兩次標(biāo)記過(guò)程饶氏。
第一次標(biāo)記:如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與 GC Roots 相連接的引用鏈讥耗,那它將會(huì)被第一次標(biāo)記;
第二次標(biāo)記:在第一次標(biāo)記后接著會(huì)進(jìn)行一次篩選疹启,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()
方法古程。在finalize()
方法中沒(méi)有重新與引用鏈建立關(guān)聯(lián)關(guān)系的,將被進(jìn)行第二次標(biāo)記喊崖。
第二次標(biāo)記成功的對(duì)象將真的會(huì)被回收挣磨,如果對(duì)象在finalize()
方法中重新與引用鏈建立了關(guān)聯(lián)關(guān)系雇逞,那么將會(huì)逃離本次回收,繼續(xù)存活茁裙。
三塘砸、回收垃圾的算法
回收垃圾的算法主要有 4 種:標(biāo)記清除算法, 標(biāo)記整理算法呜达,復(fù)制算法谣蠢,分代收集算法。下面分別介紹查近。
1、標(biāo)記清除
標(biāo)記:從 GC Roots 為起始點(diǎn)進(jìn)行掃描挤忙,如果是活動(dòng)對(duì)象霜威,則程序會(huì)在對(duì)象頭部打上標(biāo)記。
清除:對(duì)堆內(nèi)存從頭到尾進(jìn)行線性遍歷册烈,回收不可達(dá)對(duì)象戈泼。
但是,標(biāo)記清除算法會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片赏僧,導(dǎo)致無(wú)法給大對(duì)象分配內(nèi)存大猛。例如上圖中 B 與 E 之間只剩 2 格,若有一個(gè)新對(duì)象要占用 3 格淀零,則需要開辟另外的內(nèi)存或者 Full GC挽绩。
2、標(biāo)記整理
標(biāo)記:從 GC Roots 為起始點(diǎn)進(jìn)行掃描驾中,如果是活動(dòng)對(duì)象唉堪,則程序會(huì)在對(duì)象頭部打上標(biāo)記。
整理:移動(dòng)所有存活對(duì)象肩民,且按照內(nèi)存地址次序依次排列唠亚,然后將末端以后的內(nèi)存地址全部回收。
彌補(bǔ)了標(biāo)記清除算法的不足持痰,不會(huì)產(chǎn)生內(nèi)存碎片灶搜。但是需要移動(dòng)大量對(duì)象,處理效率比較低工窍。
3割卖、復(fù)制算法
將內(nèi)存劃分為大小相等的兩塊,每次只使用其中一塊移剪,當(dāng)這一塊內(nèi)存用完了就將還存活的對(duì)象復(fù)制到另一塊上面究珊,然后再把使用過(guò)的內(nèi)存空間進(jìn)行一次清理。
不會(huì)產(chǎn)生內(nèi)存碎片問(wèn)題纵苛,順序分配內(nèi)存剿涮,執(zhí)行效率高言津,但每次只使用了一半的內(nèi)存,未免有點(diǎn)浪費(fèi)取试。
4悬槽、分代收集
分代收集實(shí)際上就是將上述 3 種算法綜合起來(lái),針對(duì)不同的區(qū)域瞬浓,采用不同的方法初婆,按照對(duì)象的生命周期的不同劃分區(qū)域,采用不同的垃圾回收算法猿棉,以提高 JVM 回收效率磅叛。
Java 堆分為兩部分,Java 堆 = 新生代 + 老年代萨赁,默認(rèn)分別占堆空間為 1/3弊琴、2/3;其中杖爽,新生代 = Eden + From Survivor + To Survivor敲董,默認(rèn)為 8:1:1。這樣劃分是由于對(duì)象生存周期的特殊性慰安,針對(duì)不同的對(duì)象腋寨,采用不同的方法。
新生代使用:復(fù)制算法
老年代使用:標(biāo)記清除 或 標(biāo)記整理 算法
所有的對(duì)象都在 Eden 區(qū)創(chuàng)建化焕,由于大部分對(duì)象都是“朝生夕滅”萄窜,只有少量對(duì)象能存活下來(lái),所以在新生代采用復(fù)制算法锣杂,只有少量對(duì)象需要復(fù)制脂倦,這樣最劃算。
當(dāng) Eden 區(qū)滿了元莫,那么就會(huì)觸發(fā)一次 Young GC赖阻,也就是年輕代垃圾回收。少量有用的對(duì)象會(huì)復(fù)制到 From 區(qū)踱蠢。這樣整個(gè)Eden區(qū)就被清理干凈了火欧,可以繼續(xù)創(chuàng)建新的對(duì)象。
當(dāng) Eden 區(qū)再次被用完茎截,就再觸發(fā)一次 YoungGC苇侵,這個(gè)時(shí)候跟剛才稍稍有點(diǎn)區(qū)別。這次觸發(fā) Young GC 后企锌,會(huì)將 Eden 區(qū)與 From 區(qū)還在被使用的對(duì)象復(fù)制到 To 區(qū)榆浓,再下一次 YoungGC 的時(shí)候,則是將 Eden 區(qū)與 To 區(qū)中的還在被使用的對(duì)象復(fù)制到 From 區(qū)撕攒。
經(jīng)過(guò)若干次 YoungGC 后陡鹃,有些對(duì)象在 From 與 To 之間來(lái)回游蕩烘浦,這時(shí)候 From 區(qū)與 To 區(qū)亮出了底線(閾值),這些家伙要是到現(xiàn)在還沒(méi)掛掉萍鲸,對(duì)不起闷叉,一起復(fù)制老年代吧。
而在老年代脊阴,大部分對(duì)象任然會(huì)繼續(xù)存活下來(lái)握侧,此時(shí)采用標(biāo)記整理或者標(biāo)記清除算法,這樣最劃算嘿期。
對(duì)象如何晉升到老年代品擎?
1、經(jīng)歷一定次數(shù)的 Minor GC 任然存活的對(duì)象秽五,默認(rèn) 15 次孽查;
2、Eden 區(qū)或 Survivor 區(qū)域存放不下的對(duì)象坦喘;
3、新生成的大對(duì)象西设,直接放入老年代瓣铣。
四、常見的垃圾收集器
Serial 垃圾收集器(單線程贷揽,復(fù)制算法):
Serial 是單線程收集棠笑,進(jìn)行垃圾收集時(shí)必須暫停所有工作線程。但是它簡(jiǎn)單高效禽绪,JVM Client 模式下默認(rèn)的年輕代收集器蓖救。
ParNew 垃圾收集器(多線程,復(fù)制算法):
ParNew 是多線程收集器印屁,是 CMS 默認(rèn)的新生代垃圾回收器循捺,其他行為特點(diǎn)與 Serial 一樣。
Parallel Scavenge 垃圾收集器(多線程雄人,復(fù)制算法):
Parallel Scavenge 和 ParNew 一樣从橘,都是多線程、新生代垃圾收集器础钠。兩者的區(qū)別在于:
Parallel Scavenge 追求 CPU 吞吐量恰力,能夠在較短時(shí)間內(nèi)完成指定任務(wù),因此適合沒(méi)有交互的后臺(tái)計(jì)算旗吁;
ParNew 追求降低用戶停頓時(shí)間踩萎,適合交互式應(yīng)用。
Serial Old 垃圾收集器(單線程很钓,標(biāo)記整理算法):
Serial Old 收集器是 Serial 的老年代版本香府,都是單線程收集器董栽,都適合客戶端應(yīng)用。它們唯一的區(qū)別就是:Serial Old 工作在老年代回还,使用“標(biāo)記-整理”算法裆泳;Serial 工作在新生代,使用“復(fù)制”算法柠硕。
CMS 垃圾收集器(標(biāo)記清楚算法):
CMS (Concurrent Mark Sweep工禾,并發(fā)標(biāo)記清除) 收集器是以獲取最短回收停頓時(shí)間為目標(biāo)的收集器(追求低停頓),它在垃圾收集時(shí)蝗柔,用戶線程 和 GC 線程并發(fā)執(zhí)行闻葵,因此在垃圾收集過(guò)程中不會(huì)感到明顯的卡頓。
具體執(zhí)行過(guò)程如下圖:初始標(biāo)記 (Initial Mark) —> 并發(fā)標(biāo)記 (Concurrent Mark) —> 重新標(biāo)記 (Remark) —> 并發(fā)清除 (Concurrnet Sweep)癣丧。
初始標(biāo)記 (Initial Mark):僅僅只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對(duì)象槽畔,速度很快,需要 Stop The World胁编。
并發(fā)標(biāo)記 (Concurrent Mark):從 GC Roots 的直接關(guān)聯(lián)對(duì)象開始遍歷整個(gè)對(duì)象圖的過(guò)程厢钧,耗時(shí)較長(zhǎng),但不需要停頓用戶線程嬉橙,可與垃圾收集器線程一起并發(fā)執(zhí)行早直。
重新標(biāo)記 (Remark):該階段是為了修正并發(fā)標(biāo)記期間,因用戶程序運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄市框,這個(gè)階段需要 Stop The World霞扬,而且停頓時(shí)間通常比初始階段稍長(zhǎng)一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短枫振。
并發(fā)清除 (Concurrnet Sweep):清理刪除掉標(biāo)記階段判斷已經(jīng)死亡的對(duì)象喻圃,由于不需要移動(dòng)存活對(duì)象,所有這個(gè)階段可以和用戶線程并發(fā)執(zhí)行粪滤。
CMS 收集器是并發(fā)收集斧拍,有兩次 Stop The Words,兩次標(biāo)記额衙,因?yàn)?GC 線程和應(yīng)用線程同時(shí)執(zhí)行饮焦,好比你媽在打掃房間,你還在扔紙屑窍侧,可能產(chǎn)生新的引用關(guān)系县踢。
CMS 的缺點(diǎn):吞吐量低,無(wú)法處理浮動(dòng)垃圾伟件,導(dǎo)致頻繁 Full GC硼啤,使用“標(biāo)記-清除”算法產(chǎn)生碎片空間。
G1 垃圾收集器
G1 (Garbage-First) 是一款面向服務(wù)端應(yīng)用的垃圾收集器斧账,它弱化了新生代和老年代的概念谴返,雖然還保留了新生代和來(lái)年代的概念煞肾,但新生代和老年代不再是物理隔離的了,而是將堆劃分為一塊塊獨(dú)立的 Region(默認(rèn)將整堆劃分為 2048 個(gè) Region)嗓袱,每個(gè) Region 也不會(huì)確定地為某個(gè)代服務(wù)籍救,可以按需在年輕代和老年代之間切換。
內(nèi)存的回收是以 Region 為基本單位的渠抹,當(dāng)要進(jìn)行垃圾收集時(shí)蝙昙,首先估計(jì)每個(gè) Region 中垃圾的數(shù)量,每次都從垃圾回收價(jià)值最大的 Region 開始回收梧却,因此可以獲得最大的回收效率奇颠。
什么是回收價(jià)值最大?
價(jià)值最大就是回收耗時(shí)最短放航,回收時(shí)間就是復(fù)制的時(shí)間烈拒,存活對(duì)象越多,回收時(shí)要復(fù)制的間就越長(zhǎng)广鳍,回收的效益就越低荆几,例如同等大小的兩個(gè) Region,回收一個(gè)需要 100ms赊时,另一個(gè)需要 50ms伴郁,那么 G1 肯定優(yōu)先回收 50ms 的,這樣就保證了在有效時(shí)間內(nèi)能回收更多的堆空間蛋叼。
每個(gè) Region 都有一個(gè) Remembered Set,用來(lái)記錄該 Region 對(duì)象的引用對(duì)象所在的 Region剂陡。通過(guò)使用 Remembered Set狈涮,在做可達(dá)性分析的時(shí)候就可以避免全堆掃描。
從整體上看鸭栖, G1 是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器歌馍,從局部(兩個(gè) Region 之間)上看是基于“復(fù)制”算法實(shí)現(xiàn)的,這意味著運(yùn)行期間不會(huì)產(chǎn)生內(nèi)存空間碎片晕鹊。