Java之GC機(jī)制

1 JVM基本結(jié)構(gòu)

1)類加載器classLoader:在JVM啟動(dòng)時(shí)或者類運(yùn)行時(shí)將需要的.class文件加載到內(nèi)存中

2)內(nèi)存區(qū)域(運(yùn)行時(shí)數(shù)據(jù)區(qū)): 是在JVM運(yùn)行的時(shí)候操作所分配的內(nèi)存區(qū)

3)執(zhí)行引擎:負(fù)責(zé)執(zhí)行class文件中包含的字節(jié)碼指令

4)本地方法接口:主要是調(diào)用C/C++實(shí)現(xiàn)的本地方法及返回結(jié)果


2 JVM內(nèi)存結(jié)構(gòu)

1 )  方法區(qū):用于存儲(chǔ)類結(jié)構(gòu)信息的地方,包括常量池、靜態(tài)變量、構(gòu)造函數(shù)等。

2) Java堆(heap):存儲(chǔ)Java實(shí)例或者對象的地方。這塊是gc的主要區(qū)域

3) Java棧(stack):Java??偸呛途€程關(guān)聯(lián)的,每當(dāng)創(chuàng)建一個(gè)線程時(shí),JVM就會(huì)為這個(gè)線程創(chuàng)建一個(gè)對應(yīng)的Java棧。在這個(gè)java棧中又會(huì)包含多個(gè)棧幀,每運(yùn)行一個(gè)方法就創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作棧、方法返回值等。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)一個(gè)棧幀在java棧中入棧到出棧的過程。所以java棧是線程私有的。

4)程序計(jì)數(shù)器:用于保存當(dāng)前線程執(zhí)行的內(nèi)存地址,由于JVM是多線程執(zhí)行的,所以為了保證線程切換回來后還能恢復(fù)到原先狀態(tài),就需要一個(gè)獨(dú)立的計(jì)數(shù)器,記錄之前中斷的地方,可見程序計(jì)數(shù)器也是線程私有的

5)本地方法棧:和Java棧的作用差不多,只不過是為JVM使用到的native方法服務(wù)的


3 Java對象引用

Java對象的引用可以分為4類:強(qiáng)引用、軟引用、弱引用和虛引用。

1)強(qiáng)引用:通??梢哉J(rèn)為是通過new出來的對象,即使內(nèi)存不足,GC進(jìn)行垃圾收集的時(shí)候也不會(huì)主動(dòng)回收。

Object obj = new Object();

2)軟引用:在內(nèi)存不足的時(shí)候,GC進(jìn)行垃圾收集的時(shí)候會(huì)被GC回收

    Object obj = new Object();
    SoftReference<Object> softReference = new SoftReference<>(obj);

3)弱引用:無論內(nèi)存是否充足,GC進(jìn)行垃圾收集的時(shí)候都會(huì)回收

    Object obj = new Object();
    WeakReference<Object> weakReference = new WeakReference<>(obj);

4)虛引用:和弱引用類似,主要區(qū)別在于虛引用必須和引用隊(duì)列一起使用

    Object obj = new Object();
    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
    PhantomReference<Object> phantomReference = new PhantomReference<>(obj, referenceQueue);

引用隊(duì)列:如果軟引用和弱引用被GC回收,JVM就會(huì)把這個(gè)引用加到引用隊(duì)列里,如果是虛引用,在回收前就會(huì)被加到引用隊(duì)列里。


4 GC機(jī)制

垃圾收集器一般完成兩件事->檢測出垃圾->回收垃圾
1) 對象的年齡相關(guān)知識

對象的年齡,如果在一次垃圾回收過程中有使用該對象的,則將對象年齡加1,否則減1,當(dāng)計(jì)數(shù)為0,則進(jìn)行回收,如果年齡達(dá)到一定數(shù)字則進(jìn)入老生代??偟膩碚f內(nèi)存分配機(jī)制主要體現(xiàn)在對象創(chuàng)建之后是否仍在使用,已經(jīng)不使用的則回收,繼續(xù)使用的則對其年齡進(jìn)行更新,達(dá)到一定程度,轉(zhuǎn)移到年老代。

下圖所示是堆中內(nèi)存分配示意圖,創(chuàng)建一個(gè)對象,首先會(huì)在eden區(qū)域分配區(qū)域,如果內(nèi)存不夠,就會(huì)將年齡大的轉(zhuǎn)移到Survivor區(qū),當(dāng)survivor區(qū)域存儲(chǔ)不下,則會(huì)轉(zhuǎn)移年老代的。對于一些靜態(tài)變量不需要使用對象,直接調(diào)用的,則會(huì)被放入永生代。一般來說長期存活的對象最終會(huì)被存放到年老代,還有一種特殊情況也會(huì)被存放到年老代,就是創(chuàng)建大對象時(shí),比如數(shù)據(jù)這種需要申請連續(xù)空間的,如果空間比較大的,則會(huì)直接進(jìn)入年老代

 


2) 垃圾檢測方法

    引用計(jì)數(shù)法:給每個(gè)對象添加引用計(jì)數(shù)器,每個(gè)地方引用它,計(jì)數(shù)器就+1,失效時(shí)-1。如果兩個(gè)對象互相引用時(shí),就導(dǎo)致無法回收
     可達(dá)性分析算法:以根集對象為起始點(diǎn)進(jìn)行搜索,如果對象不可達(dá)的話就是垃圾對象。根集(Java棧中引用的對象、方法區(qū)中常量池中引用的對象、本地方法中引用的對象等。JVM在垃圾回收的時(shí)候,會(huì)檢查堆中所有對象是否被這些根集對象引用,不能夠被引用的對象就會(huì)被垃圾回收器回收。)

 
3) 垃圾回收算法

    標(biāo)記-清除:首先標(biāo)記所有需要回收的對象,在標(biāo)記完成之后統(tǒng)計(jì)回收所有被標(biāo)記的對象,它的標(biāo)記過程即為上面的可達(dá)性分析算法,清除所有被標(biāo)記的對象,缺點(diǎn):效率不足,標(biāo)記和清除效率都不高,空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導(dǎo)致大對象分配無法找到足夠的空間,提前進(jìn)行垃圾回收。
     復(fù)制算法:復(fù)制算法將可用的內(nèi)存分成兩份,每次使用其中一塊,當(dāng)這塊回收之后把未回收的復(fù)制到另一塊內(nèi)存中,然后把使用的清除。這種算法運(yùn)行簡單,解決了標(biāo)記-清除算法的碎片問題,但是這種算法代價(jià)過高,需要將可用內(nèi)存縮小一半,對象存活率較高時(shí),需要持續(xù)的復(fù)制工作,效率比較低 優(yōu)點(diǎn):保證了空間的連續(xù)性,又避免了大量的內(nèi)存空間浪費(fèi).
     標(biāo)記-整理算法:標(biāo)記過程與標(biāo)記-清除的標(biāo)記一樣,但后續(xù)不是對可回收對象進(jìn)行清理,而是讓所有的對象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。樣既可以避免不連續(xù)空間出現(xiàn),還可以避免對象存活率較高時(shí)的持續(xù)復(fù)制。這種算法適合老生代。
     分代收集算法:  分代收集算法就是目前虛擬機(jī)使用的回收算法,它解決了標(biāo)記整理不適用于老年代的問題,將內(nèi)存分為各個(gè)年代,在不同年代使用不同的算法,從而使用最合適的算法,新生代存活率低,可以使用復(fù)制算法。而老年代對象存活率高,沒有額外空間對它進(jìn)行分配擔(dān)保,所以使用標(biāo)記整理算法

 作者:chen.yu
深信服三年半工作經(jīng)驗(yàn),目前就職游戲廠商,希望能和大家交流和學(xué)習(xí),
微信公眾號:編程入門到禿頭 或掃描下面二維碼
零基礎(chǔ)入門進(jìn)階人工智能(鏈接)