图解JVM垃圾回收算法
1 簡單介紹下----->垃圾回收概念
GC中的垃圾,指的是存在于內(nèi)存中的、不會(huì)再被使用的對象。而垃圾回收就是把那些不再被使用的對象進(jìn)行清除,收回占用的內(nèi)存空間。如果不及時(shí)對內(nèi)存中的垃圾進(jìn)行清理,那么這些垃圾對象所占的內(nèi)存空間會(huì)一直保留到應(yīng)用程序結(jié)束,被保留的空間無法被其他對象使用。如果大量不會(huì)被使用的對象一致占著空間不放,如果應(yīng)用程序需要內(nèi)存空間,沒有多余的內(nèi)存空間供其使用的話,就會(huì)導(dǎo)致內(nèi)存溢出。因此,對內(nèi)存空間的管理來說,識別和清理垃圾對象是至關(guān)重要的。
但是怎么識別一個(gè)對象是否存活??也就是可達(dá)的?依據(jù)什么策略來判斷一個(gè)對象的可達(dá)性??
在java中使用根搜索算法(GC Roots Tracing)判斷一個(gè)對象是否是可達(dá)的。算法的基本思路就是通過一系列的根節(jié)點(diǎn)"GC Roots"的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個(gè)對象到GC Roots沒有引用鏈相連時(shí),則說明這個(gè)對象是不可達(dá)的。就會(huì)被判斷為可被回收的對象。
一般什么樣的對象可以作為 GCRoots呢?
在java中以下幾種對象可以作為GCRoots:
1)虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
2)方法區(qū)中的類靜態(tài)屬性引用的對象。
3)方法區(qū)中的常量引用的對象
4)本地方法棧中JNI(通常說的Native方法)引用的對象
?
2 重點(diǎn)圖解介紹----->垃圾回收算法
?下面首先會(huì)先介紹算法的主要思想,然后會(huì)使用圖解的方式直觀算法的工作流程。。。。。。。。。
?重點(diǎn)掌握其算法思想,以及其算法的優(yōu)缺點(diǎn)和適用場景。。。。。
(1) 引用計(jì)數(shù)法
引用計(jì)數(shù)法是最經(jīng)典的一種垃圾回收算法。其實(shí)現(xiàn)很簡單,對于一個(gè)A對象,只要有任何一個(gè)對象引用了A,則A的引用計(jì)算器就加1,當(dāng)引用失效時(shí),引用計(jì)數(shù)器減1.只要A的引用計(jì)數(shù)器值為0,則對象A就不可能再被使用。
雖然其思想實(shí)現(xiàn)都很簡單(為每一個(gè)對象配備一個(gè)整型的計(jì)數(shù)器),但是該算法卻存在兩個(gè)嚴(yán)重的問題:
1)? 無法處理循環(huán)引用的問題,因此在Java的垃圾回收器中,沒有使用該算法。
2)? 引用計(jì)數(shù)器要求在每次因引用產(chǎn)生和消除的時(shí)候,需要伴隨一個(gè)加法操作和減法操作,對系統(tǒng)性能會(huì)有一定的影響。
?
一個(gè)簡單的循環(huán)引用問題描述:
對象A和對象B,對象A中含有對象B的引用,對象B中含有對象A的引用。此時(shí)對象A和B的引用計(jì)數(shù)器都不為0,但是系統(tǒng)中卻不存在任何第三個(gè)對象引用A和B。也就是說A和B是應(yīng)該被回收的垃圾對象,但由于垃圾對象間的互相引用使得垃圾回收器無法識別,從而引起內(nèi)存泄漏(由于某種原因不能回收垃圾對象占用的內(nèi)存空間)。
如下圖:不可達(dá)對象出現(xiàn)循環(huán)引用,它的引用計(jì)數(shù)器不為0,
?
注意:由于引用計(jì)數(shù)器算法存在循環(huán)引用以及性能的問題,java虛擬機(jī)并未使用此算法作為垃圾回收算法。
【可達(dá)對象】:通過根對象的進(jìn)行引用搜索,最終可以到達(dá)的對象。
【不可達(dá)對象】:通過根對象進(jìn)行引用搜索,最終沒有被引用到的對象。
?
(2)標(biāo)記清除法
標(biāo)記清除法是現(xiàn)代垃圾回收算法的思想基礎(chǔ)。
標(biāo)記清除法將垃圾回收分為兩個(gè)階段:標(biāo)記階段和清除階段。
在標(biāo)記階段,首先通過根節(jié)點(diǎn),標(biāo)記所有從根節(jié)點(diǎn)開始的可達(dá)對象,因此未被標(biāo)記的對象就是未被引用的垃圾對象。然后在清除階段,清除所有未被標(biāo)記的對象。這種方法可以解決循環(huán)引用的問題,只有兩個(gè)對象不可達(dá),即使它們互相引用也無濟(jì)于事。也是會(huì)被判定位不可達(dá)對象。
標(biāo)記清除算法可能產(chǎn)生的最大的問題就是空間碎片。
如下圖所示,簡單描述了使用標(biāo)記清除法對一塊連續(xù)的內(nèi)存空間進(jìn)行回收。
從根節(jié)點(diǎn)開始(在這里僅顯示了兩個(gè)根節(jié)點(diǎn)),所有的有引用關(guān)系的對象均被標(biāo)記為存活對象(箭頭表示引用)。從根節(jié)點(diǎn)起,不可達(dá)對象均為垃圾對象。在標(biāo)記操作完成后,系統(tǒng)回收所有不可達(dá)對象。
?
從上圖可以看出,回收后的內(nèi)存空間不再連續(xù)。在對象的對空間分配過程中,尤其是大對象的內(nèi)存分配,不連續(xù)內(nèi)存空間的工作效率要低于連續(xù)空間的,這也是該算法的缺點(diǎn)。
注意:標(biāo)記清除算法先通過根節(jié)點(diǎn)標(biāo)記所有可達(dá)對象,然后清除所有不可達(dá)對象,完成垃圾回收。后面會(huì)講到標(biāo)記壓縮算法,注意兩者的區(qū)別。。。。。。
(3) 復(fù)制算法
算法思想:將原有的內(nèi)存空間分為兩塊相同的存儲(chǔ)空間,每次只使用一塊,在垃圾回收時(shí),將正在使用的內(nèi)存塊中存活對象復(fù)制到未使用的那一塊內(nèi)存空間中,之后清除正在使用的內(nèi)存塊中的所有對象,完成垃圾回收。
如果系統(tǒng)中的垃圾對象很多,復(fù)制算法需要復(fù)制的存活對象就會(huì)相對較少(適用場景)。因此,在真正需要垃圾回收的時(shí)刻,復(fù)制算法的效率是很高的。而且,由于存活對象在垃圾回收過程中是一起被賦值到另一塊內(nèi)存空間中的,因此,可確保回收的內(nèi)存空間是沒有碎片的。(優(yōu)點(diǎn))
但是復(fù)制算法的代價(jià)是將系統(tǒng)內(nèi)存空間折半,只使用一半空間,而且如果內(nèi)存空間中垃圾對象少的話,復(fù)制對象也是很耗時(shí)的,因此,單純的復(fù)制算法也是不可取的。(缺點(diǎn))
?
圖解算法回收流程:
A、B兩塊相同的內(nèi)存空間(原有內(nèi)存空間折半得到的兩塊相同大小內(nèi)存空間AB),A在進(jìn)行垃圾回收,將存活的對象復(fù)制到B中,B中的空間在復(fù)制后保持連續(xù)。完成復(fù)制后,清空A。并將空間B設(shè)置為當(dāng)前使用內(nèi)存空間。
?
在java中的新生代串行垃圾回收器中,使用了復(fù)制算法的思想,新生代分為eden空間、from空間和to空間3個(gè)部分,其中from和to空間可以看做用于復(fù)制的兩塊大小相同、可互換角色的內(nèi)存空間塊(同一時(shí)間只能有一個(gè)被當(dāng)做當(dāng)前內(nèi)存空間使用,另一個(gè)在垃圾回收時(shí)才發(fā)揮作用),from和to空間也稱為survivor空間,用于存放未被回收的對象。
【新生代對象】:存放年輕對象的堆空間,年輕對象指剛剛創(chuàng)建,或者經(jīng)歷垃圾回收次數(shù)不多的對象。
【老年代對象】:存放老年對象的堆空間。即為經(jīng)歷多次垃圾回收依然存活的對象。
? ? ?在垃圾回收時(shí),eden空間中存活的對象會(huì)被復(fù)制到未使用的survivor空間中(圖中的to),正在使用的survivor空間(圖中的from)中的年輕對象也會(huì)被復(fù)制到to空間中(大對象或者老年對象會(huì)直接進(jìn)入老年代,如果to空間已滿,則對象也會(huì)進(jìn)入老年代)。此時(shí)eden和from空間中剩余對象就是垃圾對象,直接清空,to空間則存放此次回收后存活下來的對象。
優(yōu)點(diǎn):這種復(fù)制算法保證了內(nèi)存空間的連續(xù)性,又避免了大量的空間浪費(fèi)。
注意:復(fù)制算法比較適用于新生代。因?yàn)樵谛律?#xff0c;垃圾對象通常會(huì)多于存活對象,算法的效果會(huì)比較好。
?
(4) 標(biāo)記壓縮算法
復(fù)制算法的高效性是建立在存活對象少、垃圾對象多的情況下,這種情況在新生代比較常見,
但是在老年代中,大部分對象都是存活的對象,如果還是有復(fù)制算法的話,成本會(huì)比較高。因此,基于老年代這種特性,應(yīng)該使用其他的回收算法。
標(biāo)記壓縮算法是老年代的回收算法,它在標(biāo)記清除算法的基礎(chǔ)上做了優(yōu)化。(回憶一下,標(biāo)記清除算法的缺點(diǎn),垃圾回收后內(nèi)存空間不再連續(xù),影響了內(nèi)存空間的使用效率。。。)
和標(biāo)記清除算法一樣,標(biāo)記壓縮算法也首先從根節(jié)點(diǎn)開始,對所有可達(dá)的對象做一次標(biāo)記,
但之后,它并不是簡單的清理未標(biāo)記的對象,而是將所有的存活對象壓縮到內(nèi)存空間的一端,之后,清理邊界外所有的空間。
這樣做避免的碎片的產(chǎn)生,又不需要兩塊相同的內(nèi)存空間,因此性價(jià)比高。
?
圖解其算法工作過程:
通過根節(jié)點(diǎn)標(biāo)記出所有的可達(dá)對象后,沿著虛線進(jìn)行對象的移動(dòng),將所有的可達(dá)對象移到一端,并保持他們之間的引用關(guān)系,最后,清理邊界外的空間。
?
標(biāo)記壓縮算法的最終效果等同于標(biāo)記清除算法執(zhí)行完成后,再進(jìn)行一次內(nèi)存碎片的整理,因此也稱之為標(biāo)記清除壓縮算法。
?
(5) 分代算法
前面介紹的垃圾回收算法中,并沒有一種算法可以完全替代其他算法,各自具有自己的特點(diǎn)和優(yōu)勢,因此需要根據(jù)垃圾對象的特性選擇合適的垃圾回收算法。
分代算法思想:將內(nèi)存空間根據(jù)對象的特點(diǎn)不同進(jìn)行劃分,選擇合適的垃圾回收算法,以提高垃圾回收的效率。
?
通常,java虛擬機(jī)會(huì)將所有的新建對象都放入稱為新生代的內(nèi)存空間。
新生代的特點(diǎn)是:對象朝生夕滅,大約90%的對象會(huì)很快回收,因此,新生代比較適合使用復(fù)制算法。
當(dāng)一個(gè)對象經(jīng)過幾次垃圾回收后依然存活,對象就會(huì)放入老年代的內(nèi)存空間,在老年代中,幾乎所有的對象都是經(jīng)過幾次垃圾回收后依然得以存活的,因此,認(rèn)為這些對象在一段時(shí)間內(nèi),甚至在程序的整個(gè)生命周期將是常駐內(nèi)存的。
老年代的存活率是很高的,如果依然使用復(fù)制算法回收老年代,將需要復(fù)制大量的對象。這種做法是不可取的,根據(jù)分代的思想,對老年代的回收使用標(biāo)記清除或者標(biāo)記壓縮算法可以提高垃圾回收效率。
注意:分代的思想被現(xiàn)有的虛擬機(jī)廣泛使用,幾乎所有的垃圾回收器都區(qū)分新生代和老年代。
對于新生代和老年代來說,通常新生代回收的頻率很高,但是每次回收的時(shí)間都很短,而老年代回收的頻率比較低,但是被消耗很多的時(shí)間。為了支持高頻率的新生代回收,虛擬機(jī)可能使用一種叫做卡表的數(shù)據(jù)結(jié)構(gòu),卡表為一個(gè)比特位集合,每一個(gè)比特位可以用來表示老年代的某一區(qū)域中的所有對象是否持有新生代對象的引用,
這樣以來,新生代GC時(shí),可以不用花大量時(shí)間掃描所有老年代對象,來確定每一個(gè)對象的引用關(guān)系,而可以先掃描卡表,只有當(dāng)卡表的標(biāo)記為1時(shí),才需要掃描給定區(qū)域的老年代對象,而卡表為0的所在區(qū)域的老年代對象,一定不含有新生代對象的引用。
如下圖表示:
卡表中每一位表示老年代4KB的空間,卡表記錄為0的老年代區(qū)域沒有任何對象指向新生代,只有卡表為1的區(qū)域才有對象包含新生代對象的引用,因此在新生代GC時(shí),只需要掃面卡表為1所在的老年代空間,使用這種方式,可以大大加快新生代的回收速度。
?
(6) 分區(qū)算法
算法思想:分區(qū)算法將整個(gè)堆空間劃分為連續(xù)的不同小區(qū)間,
如圖所示:
每一個(gè)小區(qū)間都獨(dú)立使用,獨(dú)立回收。
算法優(yōu)點(diǎn)是:可以控制一次回收多少個(gè)小區(qū)間
通常,相同的條件下,堆空間越大,一次GC所需的時(shí)間就越長,從而產(chǎn)生的停頓時(shí)間就越長。為了更好的控制GC產(chǎn)生的停頓時(shí)間,將一塊大的內(nèi)存區(qū)域分割成多個(gè)小塊,根據(jù)目標(biāo)的停頓時(shí)間,每次合理的回收若干個(gè)小區(qū)間,而不是整個(gè)堆空間,從而減少一個(gè)GC的停頓時(shí)間。
?
from:?http://blog.csdn.net/wen7280/article/details/54428387 《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結(jié)
以上是生活随笔為你收集整理的图解JVM垃圾回收算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM垃圾回收算法 总结及汇总
- 下一篇: JVM调优总结(4):分代垃圾回收