談談JVM垃圾回收

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

做者:fzsyw算法

一,概述緩存

 

Java運行時區域中,程序計數器,虛擬機棧,本地方法棧三個區域隨着線程的而生,隨線程而死,這幾個區域的內存分配和回收都具有肯定性,不須要過多考慮回收問題。而Java堆和方法區則不同,一個接口的多個實現類須要的內存不同,一個方法的多個分支須要的內存可能也不一眼,咱們只有在運行期,才能知道會建立的對象,這部分的內存分配和回收,是垃圾回收器所關注的。垃圾回收器須要完成三個問題:那些內存須要回收;何時回收以及如何回收。安全

二,哪些垃圾須要回收併發

 

垃圾回收的基本思想是考察一個對象的可達性,即從根節點開始是否能夠訪問到這個對象,若是能夠,則說明對象正在被使用,相反若是從根節點沒法訪問到這個對象,說明對象已經再也不使用了,通常來講此對象就是須要被回收的。這個算法爲根搜索算法。ide

 

2.1可達性分析優化

 

可是實際中,一個不可達的對象有可能在某種條件下「復活」本身,那麼對它的回收就是不合理的。爲此給出一個對象可達性狀態的定義,並規定了在什麼狀態下能夠安全的回收對象。可達性對象包含了如下三種狀態。this

  • 可達的:從根節點開始,按照引用節點,能夠搜索到這個對象線程

  • 可復活的:對象的全部引用都被釋放,可是對象可能在finalize()方法中復活本身。對象

  • 不可達的:對象的finalize()方法被調用,而且沒有復活,那麼就進入不可達狀態。不可達的對象不可能會被「復活」,由於finalize()方法只能調用一次。blog

     

    •  
    /** *  * <p>Description: 1.對象被GC時,能夠經過finalize拯救 2.finalize只被調用一次 </p>   * @date 2019年8月25日   * @version 1.0 */public class FinalizeTest {
    private static FinalizeTest currentObj;
    @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize invoke"); //從新引用 currentObj = this; }
    public void alive() { System.out.println("live"); }
    public static void main(String[] args) throws InterruptedException { currentObj = new FinalizeTest();
    currentObj = null; System.gc(); //finalize優先級地,先等待 Thread.sleep(500); if(currentObj == null) { System.out.println("dead"); }else { currentObj.alive(); }
    currentObj = null; System.gc(); //finalize優先級地,先等待 Thread.sleep(500); if(currentObj == null) { System.out.println("dead"); }else { currentObj.alive(); } }}

上面代碼有一處同樣的斷碼片斷,可是獲得的結果卻並不相同,一次對象「拯救復活」成功,另外一次失敗,那麼就能夠被正常回收。

能夠做爲GC Roots包括下面幾種:

  • 虛擬機棧(棧幀中的本地表量表)中引用的對象

  • 方法區中類靜態屬性引用的對象

  • 方法區中常量引用的對象

  • 本地方法棧中JNI引用(即通常Native的方法)的對象

  2.2 四種引用類型

 

在JDK1.2以後對引用進行了擴充,分爲強引用,軟引用,弱引用,虛引用4種,這四種強度一次減弱。經過對引用的擴充,能夠依據內存的使用來描述這樣的對象:當內存足夠,則保留內存中;若是內存空間進行垃圾回收後仍是很緊張,則能夠拋棄這類對象。不少系統的緩存功能符合這樣的應用場景。

  • 強引用
    在Java中最多見的就是強引用, 把一個對象賦給一個引用變量,這個引用變量就是一個強引用。當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即便該對象之後永遠都不會被用到JVM也不會回收。所以強引用是形成Java內存泄漏的主要緣由之一。

  • 軟引用

    軟引用須要用SoftReference類來實現,對於只有軟引用的對象來講,當系統內存足夠時它不會被回收,當系統內存空間不足時它會被回收。軟引用一般用在對內存敏感的程序中。

  • 弱引用

    弱引用須要用WeakReference類來實現,它比軟引用的生存期更短,對於只有弱引用的對象來講,只要垃圾回收機制一運行,無論 JVM 的內存空間是否足夠,總會回收該對象佔用的內存。

  • 虛引用

    虛引用須要PhantomReference類來實現,它不能單獨使用,必須和引用隊列聯合使用。虛引用的主要做用是跟蹤對象被垃圾回收的狀態。

 

三,何時回收

 

 

按HotSpot VM的serial GC的實現來看觸發條件主要分爲如下幾種:

  • young GC:當young gen中的eden區分配滿的時候觸發。注意young GC中有部分存活對象會晉升到old gen,因此young GC後old gen的佔用量一般會有所升高。

  • full GC:當準備要觸發一次young GC時,若是發現統計數聽說以前young GC的平均晉升大小比目前old gen剩餘的空間大,則不會觸發young GC而是轉爲觸發full GC(由於HotSpot VM的GC裏,除了CMS的concurrent collection以外,其它能收集old gen的GC都會同時收集整個GC堆,包括young gen,因此不須要事先觸發一次單獨的young GC);或者,若是有perm gen的話,要在perm gen分配空間但已經沒有足夠空間時,也要觸發一次full GC;或者System.gc()、heap dump帶GC,默認也是觸發full GC。

HotSpot VM裏其它非併發GC的觸發條件複雜一些,不過大體的原理與上面說的其實同樣。併發GC的觸發條件就不太同樣。以CMS GC爲例,它主要是定時去檢查old gen的使用量,當使用量超過了觸發比例就會啓動一次CMS GC,對old gen作併發收集。

 

 

四,如何回收

 

 

如何回收主要就涉及到垃圾回收的算法了。下面介紹幾種垃圾回收算法的思想。

 

 4.1 標記清除法(Mark-Sweep)

 

標記清除算法是現代垃圾回收算法的思想基礎。它主要分爲兩個階段:標記階段和清除階段。在標記階段,首先經過根節點,標記全部從根節點開始的可達隊對象,所以未被標記的對象就是未被引用的垃圾對象。而後在清除階段,清除全部的未被標記的對象。

標記清除算法的不足有:效率的問題和標記清除後產生的大量不連續的內存碎片。而內存碎片太多可能會致使在分配大對象時,沒法找到連續的內存而不得不提早觸發另一次垃圾回收。

 

4.2  複製算法(Coping)

 

複製算法的核心思想是:將原有的內存空間分爲兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內存中存活對象複製到未使用的內存塊中,以後清除正在使用的內存塊中的全部對象,交換兩個內存的角色,完成垃圾回收。

若是系統中的待回收的對象不少,複製算法須要複製的存活對象就會相對較少,真正的垃圾回收時刻,複製算法的效率就會很高。並且對象是在垃圾回收過程當中的,統一複製到新的內存空間,再清除原來使用的內存,所以能夠確保回收後的內存空間是沒有碎片的。可是另外一方面,複製算法的代價是須要使用更多的內存空間。

複製算法比較適用於新生代。由於新生代垃圾對象一般多餘存活對象,複製算法的效率會比較高。

4.3 標記整理算法(Mark Compact)

 

在老年代,大部分的對象都是存活對象。若是依然用複製算法,因爲存活的對象多,複製的成本也將提升。所以基於老年代的垃圾回收特性,須要使用其餘的算法。標記整理算法是一種老年代的回收算法。它在標記算法的基礎上作了一些優化。和標記清除算法同樣,它也是從更節點開始,可是並非清除未標記的對象,而是將存活的對象壓縮到內存的一邊,以後清除邊界外全部空間。這種方法避免了碎片的產生,又不須要過多的內存空間,所以性價比比較高。

標記整理法的最終效果等同於標記清除算法執行完成後,再進行一次內存碎片的整理,所以也能夠把它稱爲標記清除整理(MarkSweepComact)。

4.4 分代算法(Generational Collecting)

 

分代算法是根據對象存活週期不一樣將內存化爲幾塊。通常是把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最合適的收集算法。新生代中的特色是對象朝生夕死,大約90%的新建對象會被回收,所以新生代適合複製算法。當一個對象通過幾回回收後依然存活,對象就會被放入老年代的內存空間。在老年代中能夠認爲對象在一段時間內,甚至在程序的整個生命週期,是常駐內存的,能夠對老年代使用標記清除和標記整理算法。

對於新生代和老年代來講,一般新生代的回收頻率很高,可是每次回收的耗時都很短,而老年代回收的頻率比較低,可是會消耗更多的時間。

4.5 分區算法(Region)

 

通常來講,相同條件下,堆空間越大,一次GC所須要的事件越長,從而產生的停頓也越長。爲了更好的靠之停頓時間,將一塊大的內存區域分割成多個大小形同的小區域,依據目標的停頓時間,每次回收若干個小區間,而不是整個堆空間,從而減小一次GC所產生的停頓。分區算法是將整個堆空間劃分爲連續的不一樣小區間。每一個小區間獨立使用,獨立回收。

相關文章
相關標籤/搜索