Java之GC機制

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

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

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

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

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


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

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

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

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

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

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


3 Java對象引用

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

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

Object obj = new Object();

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

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

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

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

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

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

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


4 GC機制

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

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

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

 


2) 垃圾檢測方法

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

 
3) 垃圾回收算法

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

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