JVM垃圾回收機制

Java語言出來以前,你們都在拼命的寫C或者C++的程序,而此時存在一個很大的矛盾,C++等語言建立對象要不斷的去開闢空間,不用的時候有須要不斷的去釋放控件,既要寫構造函數,又要寫析構函數,不少時候都在重複的allocated,而後不停的~析構。因而,有人就提出,能不能寫一段程序在實現這塊功能,每次建立,釋放控件的時候複用這段代碼,而無需重複的書寫呢?java

1960年 基於MIT的Lisp首先提出了垃圾回收的概念,用於處理C語言等不停的析構操做,而這時Java尚未出世呢!因此實際上GC並非Java的專利,GC的歷史遠遠大於Java的歷史!算法

那究竟GC爲咱們作了什麼操做呢?緩存

 

一、    哪些內存須要回收?網絡

二、    何時回收?多線程

三、    如何回收?併發

 

這時候有人就會疑惑了,既然GC已經爲咱們解決了這個矛盾,咱們還須要學習GC麼?固然固然是確定的,那究竟何時咱們還須要用到的呢?oracle

 

一、    排查內存溢出jvm

二、    排查內存泄漏分佈式

三、    性能調優,排查併發瓶頸ide

 

咱們知道,GC主要處理的是對象的回收操做,那麼何時會觸發一個對象的回收的呢?

一、    對象沒有引用

二、    做用域發生未捕獲異常

三、    程序在做用域正常執行完畢

四、    程序執行了System.exit()

五、    程序發生意外終止(被殺進程等)

其實,咱們最容易想到的就是當對象沒有引用的時候會將這個對象標記爲可回收對象,那麼如今就有一個問題,是否是這個對象被賦值爲null之後就必定被標記爲可回收對象了呢?咱們來看一個例子:

 

package com.yhj.jvm.gc.objEscape.finalizeEscape;

 

import com.yhj.jvm.gc.objEscape.pojo.FinalizedEscapeTestCase;

 

 

/**

 * @Described:逃逸分析測試

 * @author YHJ create at 2011-12-24 下午05:08:09

 * @FileNmae com.yhj.jvm.gc.finalizeEscape.FinalizedEscape.java

 */

public class FinalizedEscape {

    public static void main(String[] args) throwsInterruptedException {

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

       FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase();

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

       FinalizedEscapeTestCase.caseForEscape=null;

       System.gc();

       Thread.sleep(100);

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

    }

}

package com.yhj.jvm.gc.objEscape.pojo;

/**

 * @Described:逃逸分析測試用例

 * @author YHJ create at 2011-12-24 下午05:07:05

 * @FileNmae com.yhj.jvm.gc.pojo.TestCaseForEscape.java

 */

public class FinalizedEscapeTestCase {

 

    public static FinalizedEscapeTestCase caseForEscape = null;

    @Override

    protected void finalize() throws Throwable {

       super.finalize();

       System.out.println("哈哈,我已逃逸!");

       caseForEscape = this;

    }

}

 

程序的運行結果回事什麼樣子的呢?

咱們來看這段代碼

 

一、  System.out.println(FinalizedEscapeTestCase.caseForEscape);

二、  FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase();

三、  System.out.println(FinalizedEscapeTestCase.caseForEscape);

四、  FinalizedEscapeTestCase.caseForEscape=null;

五、  System.gc();

六、  Thread.sleep(100);

七、    System.out.println(FinalizedEscapeTestCase.caseForEscape);

 

一、    當程序執行第一行是,由於這個對象沒有值,結果確定是null

二、    程序第二行給該對象賦值爲新開闢的一個對象

三、    第三行打印的時候,確定是第二行對象的hash代碼

四、    第四行將該對象從新置爲null

五、    第五行觸發GC

六、    爲了保證GC可以順利執行完畢,第六行等待100毫秒

七、    第七行打印對應的值,回事null麼?必定會是null麼?

咱們來看一下對應的運行結果


 

 本例中打印了

GC的日誌,讓咱們看的更清晰一點,咱們很清晰的看出,最後一句打印的不是null,而且子啊以前,還出現了逃逸的字樣。說明這個對象逃逸了,在垃圾回收以前逃逸了,咱們再來看這個pojo的寫法,就會發現,咱們重寫了方法finalize,而這個方法就至關於C++中的析構方法,在GC回收以前,會先調用一次這個方法,而這個方法又將this指針指向他本身,所以得以成功逃逸!可見,並非這個對象被賦值爲null以後就必定被標記爲可回收,有可能會發生逃逸!

 

下面咱們來看一下幾種垃圾收集算法

一、              在JDK1.2以前,使用的是引用計數器算法,即當這個類被加載到內存之後,就會產生方法區,堆棧、程序計數器等一系列信息,當建立對象的時候,爲這個對象在堆棧空間中分配對象,同時會產生一個引用計數器,同時引用計數器+1,當有新的引用的時候,引用計數器繼續+1,而當其中一個引用銷燬的時候,引用計數器-1,當引用計數器被減爲零的時候,標誌着這個對象已經沒有引用了,能夠回收了!這種算法在JDK1.2以前的版本被普遍使用,可是隨着業務的發展,很快出現了一個問題

當咱們的代碼出現下面的情形時,該算法將沒法適應

a)         ObjA.obj = ObjB

b)         ObjB.obj - ObjA

                 這樣的代碼會產生以下引用情形 objA指向objB,而objB又指向objA,這樣當其餘全部的引用都消失了以後,objA和objB還有一個相互的引用,也就是說兩個對象的引用計數器各爲1,而實際上這兩個對象都已經沒有額外的引用,已是垃圾了。

               


 

 

二、              根搜索算法

                   根搜索算法是從離散數學中的圖論引入的,程序把全部的引用關係看做一張圖,從一個節點GC ROOT開始,尋找對應的引用節點,找到這個節點之後,繼續尋找這個節點的引用節點,當全部的引用節點尋找完畢以後,剩餘的節點則被認爲是沒有被引用到的節點,即無用的節點。

 



 

 

目前java中可做爲GC Root的對象有

一、    虛擬機棧中引用的對象(本地變量表)

二、    方法區中靜態屬性引用的對象

三、    方法區中常量引用的對象

四、    本地方法棧中引用的對象(Native對象)

說了這麼多,其實咱們能夠看到,全部的垃圾回收機制都是和引用相關的,那咱們來具體的來看一下引用的分類,到底有哪些類型的引用?每種引用都是作什麼的呢?

Java中存在四種引用,每種引用以下:

一、  強引用

只要引用存在,垃圾回收器永遠不會回收

Object obj = new Object();

//可直接經過obj取得對應的對象 如obj.equels(new Object());

而這樣 obj對象對後面new Object的一個強引用,只有當obj這個引用被釋放以後,對象纔會被釋放掉,這也是咱們常常所用到的編碼形式。

二、  軟引用

非必須引用,內存溢出以前進行回收,能夠經過如下代碼實現

Object obj = new Object();

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

obj = null;

sf.get();//有時候會返回null

這時候sf是對obj的一個軟引用,經過sf.get()方法能夠取到這個對象,固然,當這個對象被標記爲須要回收的對象時,則返回null;
軟引用主要用戶實現相似緩存的功能,在內存足夠的狀況下直接經過軟引用取值,無需從繁忙的真實來源查詢數據,提高速度;當內存不足時,自動刪除這部分緩存數據,從真正的來源查詢這些數據。

三、  弱引用

第二次垃圾回收時回收,能夠經過以下代碼實現

Object obj = new Object();

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

obj = null;

wf.get();//有時候會返回null

wf.isEnQueued();//返回是否被垃圾回收器標記爲即將回收的垃圾

弱引用是在第二次垃圾回收時回收,短期內經過弱引用取對應的數據,能夠取到,當執行過第二次垃圾回收時,將返回null。

弱引用主要用於監控對象是否已經被垃圾回收器標記爲即將回收的垃圾,能夠經過弱引用的isEnQueued方法返回對象是否被垃圾回收器

四、  虛引用(幽靈/幻影引用)

           垃圾回收時回收,沒法經過引用取到對象值,能夠經過以下代碼實現

Object obj = new Object();

PhantomReference<Object> pf = new PhantomReference<Object>(obj);

obj=null;

pf.get();//永遠返回null

pf.isEnQueued();//返回從內存中已經刪除

虛引用是每次垃圾回收的時候都會被回收,經過虛引用的get方法永遠獲取到的數據爲null,所以也被成爲幽靈引用。

虛引用主要用於檢測對象是否已經從內存中刪除。

在上文中已經提到了,咱們的對象在內存中會被劃分爲5塊區域,而每塊數據的回收比例是不一樣的,根據IBM的統計,數據以下圖所示:

 

 



 

 咱們知道,方法區主要存放類與類之間關係的數據,而這部分數據被加載到內存以後,基本上是不會發生變動的,

Java堆中的數據基本上是朝生夕死的,咱們用完以後要立刻回收的,而Java棧和本地方法棧中的數據,由於有後進先出的原則,當我取下面的數據以前,必需要把棧頂的元素出棧,所以回收率可認爲是100%;而程序計數器咱們前面也已經提到,主要用戶記錄線程執行的行號等一些信息,這塊區域也是被認爲是惟一一塊不會內存溢出的區域。在SunHostSpot的虛擬機中,對於程序計數器是不回收的,而方法區的數據由於回收率很是小,而成本又比較高,通常認爲是「性價比」很是差的,因此Sun本身的虛擬機HotSpot中是不回收的!可是在如今高性能分佈式J2EE的系統中,咱們大量用到了反射、動態代理、CGLIB、JSP和OSGI等,這些類頻繁的調用自定義類加載器,都須要動態的加載和卸載了,以保證永久帶不會溢出,他們經過自定義的類加載器進行了各項操做,所以在實際的應用開發中,類也是被常常加載和卸載的,方法區也是會被回收的!可是方法區的回收條件很是苛刻,只有同時知足如下三個條件纔會被回收!

 

一、全部實例被回收

二、加載該類的ClassLoader被回收

三、Class對象沒法經過任何途徑訪問(包括反射)

好了,咱們如今切入正題,Java1.2以前主要經過引用計數器來標記是否須要垃圾回收,而1.2以後都使用根搜索算法來收集垃圾,而收集後的垃圾是經過什麼算法來回收的呢?

一、    標記-清除算法

二、    複製算法

三、    標記-整理算法

咱們來逐一過一下

一、    標記-清除算法

        



 

 

標記-清除算法採用從根集合進行掃描,對存活的對象對象標記,標記完畢後,再掃描整個空間中未被標記的對象,進行回收,如上圖所示。

標記-清除算法不須要進行對象的移動,而且僅對不存活的對象進行處理,在存活對象比較多的狀況下極爲高效,但因爲標記-清除算法直接回收不存活的對象,所以會形成內存碎片!

二、    複製算法

 

 



 
 
 
 

 複製算法採用從根集合掃描,並將存活對象複製到一塊新的,沒有使用過的空間中,這種算法當控件存活的對象比較少時,極爲高效,可是帶來的成本是須要一塊內存交換空間用於進行對象的移動。也就是咱們前面提到的

s0 s1等空間。

 

三、    標記-整理算法

 

 



 
 
 

 標記

-整理算法採用標記-清除算法同樣的方式進行對象的標記,但在清除時不一樣,在回收不存活的對象佔用的空間後,會將全部的存活對象往左端空閒空間移動,並更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,所以成本更高,可是卻解決了內存碎片的問題。

 

咱們知道,JVM爲了優化內存的回收,進行了分代回收的方式,對於新生代內存的回收(minor GC)主要採用複製算法,下圖展現了minor GC的執行過程。

 

 



 
 

 對於新生代和舊生代,

JVM可以使用不少種垃圾回收器進行垃圾回收,下圖展現了不一樣生代不通垃圾回收器,其中兩個回收器之間有連線表示這兩個回收器能夠同時使用。

 

 

 



 

 而這些垃圾回收器又分爲串行回收方式、並行回收方式合併發回收方式執行,分別運用於不一樣的場景。以下圖所示

 

 

 



 

 下面咱們來逐一介紹一下每一個垃圾回收器。

 

一、    Serial收集器

看名字咱們均可以看的出來,這個屬於串行收集器。其運行示意圖以下

 



 
 

 Serial

收集器是歷史最悠久的一個回收器,JDK1.3以前普遍使用這個收集器,目前也是ClientVM下 ServerVM 4核4GB如下機器的默認垃圾回收器。串行收集器並非只能使用一個CPU進行收集,而是當JVM須要進行垃圾回收的時候,須要中斷全部的用戶線程,知道它回收結束爲止,所以又號稱「Stop The World」 的垃圾回收器。注意,JVM中文名稱爲java虛擬機,所以它就像一臺虛擬的電腦同樣在工做,而其中的每個線程就被認爲是JVM的一個處理器,所以你們看到圖中的CPU0、CPU1實際爲用戶的線程,而不是真正機器的CPU,你們不要誤解哦。

 

串行回收方式適合低端機器,是Client模式下的默認收集器,對CPU和內存的消耗不高,適合用戶交互比較少,後臺任務較多的系統。

Serial收集器默認新舊生代的回收器搭配爲Serial+ SerialOld

二、    ParNew收集器

ParNew收集器其實就是多線程版本的Serial收集器,其運行示意圖以下

 

 



 
 
 

 一樣有

Stop The World的問題,他是多CPU模式下的首選回收器(該回收器在單CPU的環境下回收效率遠遠低於Serial收集器,因此必定要注意場景哦),也是Server模式下的默認收集器。

 

三、    ParallelScavenge

ParallelScavenge又被稱爲是吞吐量優先的收集器,器運行示意圖以下

 

 



 
 

 ParallelScavenge

所提到的吞吐量=程序運行時間/(JVM執行回收的時間+程序運行時間),假設程序運行了100分鐘,JVM的垃圾回收佔用1分鐘,那麼吞吐量就是99%。在當今網絡告訴發達的今天,良好的響應速度是提高用戶體驗的一個重要指標,多核並行雲計算的發展要求程序儘量的使用CPU和內存資源,儘快的計算出最終結果,所以在交互很少的雲端,比較適合使用該回收器。

 

四、    ParallelOld

ParallelOld是老生代並行收集器的一種,使用標記整理算法、是老生代吞吐量優先的一個收集器。這個收集器是JDK1.6以後剛引入的一款收集器,咱們看以前那個圖之間的關聯關係能夠看到,早期沒有ParallelOld以前,吞吐量優先的收集器老生代只能使用串行回收收集器,大大的拖累了吞吐量優先的性能,自從JDK1.6以後,才能真正作到較高效率的吞吐量優先。其運行示意圖以下

 

 



 
  

 五、

    SerialOld

 

SerialOld是舊生代Client模式下的默認收集器,單線程執行;在JDK1.6以前也是ParallelScvenge回收新生代模式下舊生代的默認收集器,同時也是併發收集器CMS回收失敗後的備用收集器。其運行示意圖以下

 

 



 

 六、

    CMS

 

CMS又稱響應時間優先(最短回收停頓)的回收器,使用併發模式回收垃圾,使用標記-清除算法,CMS對CPU是很是敏感的,它的回收線程數=(CPU+3)/4,所以當CPU是2核的實惠,回收線程將佔用的CPU資源的50%,而當CPU核心數爲4時僅佔用25%。他的運行示意圖以下

 

 



 

 CMS

模式主要分爲4個過程

 

 

 


  

 在初始標記的時候,須要中斷全部用戶線程,在併發標記階段,用戶線程和標記線程

併發執行,而在這個過程當中,隨着內存引用關係的變化,可能會發生原來標記的對象被釋放,進而引起新的垃圾,所以可能會產生一系列的浮動垃圾,不能被回收。

 

CMS 爲了確保可以掃描到全部的對象,避免在Initial Marking 中還有未標識到的對象,採用的方法爲找到標記了的對象,並將這些對象放入Stack 中,掃描時尋找此對象依賴的對象,若是依賴的對象的地址在其以前,則將此對象進行標記,並同時放入Stack 中,如依賴的對象地址在其以後,則僅標記該對象。

在進行Concurrent Marking 時minor GC 也可能會同時進行,這個時候很容易形成舊生代對象引用關係改變,CMS 爲了應對這樣的並發現象,提供了一個Mod Union Table 來進行記錄,在這個Mod Union Table中記錄每次minor GC 後修改了的Card 的信息。這也是ParallelScavenge不能和CMS一塊兒使用的緣由。

CMS產生浮動垃圾的狀況請見以下示意圖

 



 

 

在運行回收事後,c就變成了浮動垃圾。

因爲CMS會產生浮動垃圾,當回收事後,浮動垃圾若是產生過多,同時由於使用標記-清除算法會產生碎片,可能會致使回收事後的連續空間仍然不能容納新生代移動過來或者新建立的大資源,所以會致使CMS回收失敗,進而觸發另一次FULL GC,而這時候則採用SerialOld進行二次回收。

同時CMS由於可能產生浮動垃圾,而CMS在執行回收的同時新生代也有可能在進行回收操做,爲了保證舊生代可以存放新生代轉移過來的數據,CMS在舊生代內存到達所有容量的68%就觸發了CMS的回收!

七、    GarbageFirst(G1 )

咱們再來看垃圾回收器的總圖,剛纔咱們能夠看到,我在圖上標記了一個?,其實這是一個新的垃圾回收器,既能夠回收新生代也能夠回收舊生代,SunHotSpot 1.6u14以上EarlyAccess版本加入了這個回收器,sun公司預期SunHotSpot1.7發佈正式版,他是商用高性能垃圾回收器,經過從新劃份內存區域,整合優化CMS,同時注重吞吐量和響應時間,可是杯具的是被oracle收購以後這個收集器屬於商用收費收集器,所以目前基本上沒有人使用,咱們在這裏也就很少介紹,更多信息能夠參考oracle新版本JDK說明。

下面咱們再來看下JVM的一些內存分配與回收策略

一、    優先在Edon上分配對象

 

代碼示例

package com.yhj.jvm.gc.edenFirst;

/**

 * @Described:Edon優先劃分對象測試

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc

 * Edon s0 s1 old

 *   8  1   1  10

 * @author YHJ create at 2012-1-3 下午04:44:43

 * @FileNmae com.yhj.jvm.gc.edenFirst.EdonFirst.java

 */

public class EdonFirst {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-3 下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4;

       testCase1 = new byte[2*ONE_MB];

       testCase2 = new byte[2*ONE_MB];

       testCase3 = new byte[2*ONE_MB];

//     testCase1 = null;

//     testCase2 = null;

//     testCase3 = null;

       testCase4 = new byte[2*ONE_MB];

    }

 

}

運行結果

 



 

 結果分析

 

從運行結果咱們能夠很清晰的看到,eden有8MB的存儲控件(經過參數配置),前6MB的數據優先分配到eden區域,當下一個2MB存放時,因空間已滿,觸發一次GC,可是這部分數據由於沒有回收(引用還在,當賦值爲null後則不會轉移),數據會被複制到s0區域,可是s0區域不夠存儲,所以直接放入老生代區域,新的2MB數據存放在eden區域

二、    大對象直接進入老生代

 

代碼示例

package com.yhj.jvm.gc.bigObjIntoOld;

/**

 * @Described:大對象直接進入老生代測試

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc

 * Edon s0 s1 old

 *   8  1   1  10

 * @author YHJ create at 2012-1-3 下午05:28:47

 * @FileNmae com.yhj.jvm.gc.bigObjIntoOld.BigObjIntoOld.java

 */

public class BigObjIntoOld {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-3 下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4;

       testCase1 = new byte[8*ONE_MB];

//     testCase2 = new byte[2*ONE_MB];

//     testCase3 = new byte[2*ONE_MB];

//     testCase1 = null;

//     testCase2 = null;

//     testCase3 = null;

//     testCase4 = new byte[2*ONE_MB];

    }

 

}

運行結果

 



 

 結果分析

 

咱們看到,沒有觸發GC日誌,而數據是直接進入老生代的

三、    年長者(長期存活對象)進入老生代

 

代碼示例:

package com.yhj.jvm.gc.longLifeTimeIntoOld;

/**

 * @Described:當年齡大於必定值的時候進入老生代  默認值15歲

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:MaxTenuringThreshold=1-XX:+PrintGCDetails -verbose:gc

 * Edon s0 s1 old  age

 *   8  1   1  10   1

 * @author YHJ create at 2012-1-3 下午05:39:16

 * @FileNmaecom.yhj.jvm.gc.longLifeTimeIntoOld.LongLifeTimeIntoOld.java

 */

public class LongLifeTimeIntoOld {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-3 下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4;

       testCase1 = new byte[1*ONE_MB/4];

       testCase2 = new byte[7*ONE_MB+3*ONE_MB/4];

       testCase2 = null;

       testCase3 = new byte[7*ONE_MB+3*ONE_MB/4];

       testCase3 = null;

       testCase4 = new byte[ONE_MB];

    }

 

}

 

運行結果

 



 

 結果分析

 

從代碼中咱們能夠看到,當testCase1劃分爲0.25MB數據,進行屢次大對象建立以後,testCase1應該在GC執行以後被複制到s0區域(s0足以容納testCase1),可是咱們設置了對象的年齡爲1,即超過1歲便進入老生代,所以GC執行2次後testCase1直接被複制到了老生代,而默認進入老生代的年齡爲15。咱們經過profilter的監控工具能夠很清楚的看到對象的年齡,如圖所示

 

 



 

 右側的年代數目就是對象的年齡

 

四、    羣體效應(大批中年對象進入老生代)

 

代碼示例

package com.yhj.jvm.gc.dynamicMoreAVG_intoOld;

/**

 * @Described:s0佔用空間到達50%直接進入老生代

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:MaxTenuringThreshold=15-XX:+PrintGCDetails -verbose:gc

 * Edon s0 s1 old  age

 *   8  1   1  10  15

 *   0.5 0  0   7.5

 *   7.5 0.5 0  7.5

 *   7.5  0  0 8

 * @author YHJ create at 2012-1-3 下午05:50:40

 * @FileNmae com.yhj.jvm.gc.dynamicMoreAVG_intoOld.MoreAVG_intoOld.java

 */

public class MoreAVG_intoOld {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-3 下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4;

       testCase1 = new byte[7*ONE_MB+ONE_MB/2];

       testCase2 = new byte[ONE_MB/2];

       testCase3 = new byte[7*ONE_MB+ONE_MB/2];

       testCase3 = null;

       testCase4 = new byte[7*ONE_MB+ONE_MB/2];

      

//     testCase1 = new byte[7*ONE_MB+3*ONE_MB/4];

//     testCase2 = new byte[ONE_MB/4];

//     testCase3 = new byte[7*ONE_MB+3*ONE_MB/4];

      

    }

 

}

運行結果

 



 

 結果分析

 

咱們看到,當建立後testCase3,testCase2被移動到s0區域,當被釋放後,繼續建立testCase3,按理說testCase2應該移動到s1區域,可是由於超過了s1區域的1/2,所以直接進入老生代

五、    擔保GC(擔保minorGC)

擔保GC就是擔保minorGC可以知足當前的存儲空間,而無需觸發老生代的回收,因爲大部分對象都是朝生夕死的,所以,在實際開發中這種很起效,可是也有可能會發生擔保失敗的狀況,當擔保失敗的時候會觸發FullGC,可是失敗畢竟是少數,所以這種通常是很划算的。

 

代碼示例

package com.yhj.jvm.gc.securedTransactions;

/**

 * @Described:擔保交易測試

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc-XX:-HandlePromotionFailure  無擔保

 * VM params : -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc-XX:+HandlePromotionFailure  有擔保

 * Edon s0 s1 old  

 *   8  1   1  10  

 * @author YHJ create at 2012-1-3 下午06:11:17

 * @FileNmaecom.yhj.jvm.gc.securedTransactions.SecuredTransactions.java

 */

public class SecuredTransactions {

 

    private final static int ONE_MB = 1024*1024;

 

    /**

     * @param args

     * @Author YHJ create at 2012-1-3 下午04:44:38

     */

    public static void main(String[] args) {

       @SuppressWarnings("unused")

       byte[] testCase1,testCase2,testCase3,testCase4,testCase5,testCase6,testCase7;

       testCase1 = new byte[2*ONE_MB];

       testCase2 = new byte[2*ONE_MB];

       testCase3 = new byte[2*ONE_MB];

       testCase1 = null;

       testCase4 = new byte[2*ONE_MB];

       testCase5 = new byte[2*ONE_MB];

       testCase6 = new byte[2*ONE_MB];

       testCase4 = null;

       testCase5 = null;

       testCase6 = null;

       testCase7 = new byte[2*ONE_MB];

      

    }

 

}

 運行結果

一、  無擔保

 

 

 二、



   有擔保

 

 

 



 

 

 結果分析

 

 

 

咱們能夠很清楚的看到,當無擔保的時候,觸發了一次FullGC 而有擔保的狀況下,只有monorGC則完成了回收,大大提高了效率。

當咱們註釋掉對應的代碼

 

//     testCase4 = null;

//     testCase5 = null;

//     testCase6 = null;

 

的時候,就會引起擔保失敗,以下圖所示

 

 


 JVM

默認狀況是是開啓擔保的,無需設置參數。

 

  • 大小: 89.7 KB
  • 大小: 20.3 KB
  • 大小: 40.7 KB
  • 大小: 47.6 KB
  • 大小: 26.7 KB
  • 大小: 17.2 KB
  • 大小: 33.6 KB
  • 大小: 47.3 KB
  • 大小: 55.4 KB
  • 大小: 21.8 KB
  • 大小: 18.6 KB
  • 大小: 20.5 KB
  • 大小: 45.2 KB
  • 大小: 18.5 KB
  • 大小: 20.6 KB
  • 大小: 23.5 KB
  • 大小: 46.1 KB
  • 大小: 69.7 KB
  • 大小: 53.5 KB
  • 大小: 64.2 KB
  • 大小: 91 KB
  • 大小: 71.7 KB
  • 大小: 42.7 KB
  • 大小: 64.7 KB
  • 大小: 60.4 KB
  • 大小: 21.3 KB
相關文章
相關標籤/搜索