Java語言出來之前,大家都在拼命的寫C或者C++的程式,而此時存在一個很大的矛盾,C++等語言建立物件要不斷的去開闢空間,不用的時候有需要不斷的去釋放控制元件,既要寫建構函式,又要寫解構函式,很多時候都在重複的allocated,然後不停的~析構。於是,有人就提出,能不能寫一段程式在實現這塊功能,每次建立,釋放控制元件的時候複用這段程式碼,而無需重複的書寫呢?
1960年 基於MIT的Lisp首先提出了垃圾回收的概念,用於處理C語言等不停的析構操作,而這時Java還沒有出世呢!所以實際上GC並不是Java的專利,GC的歷史遠遠大於Java的歷史!
那究竟GC為我們做了什麼操作呢?
1、 哪些記憶體需要回收?
2、 什麼時候回收?
3、 如何回收?
這時候有人就會疑惑了,既然GC已經為我們解決了這個矛盾,我們還需要學習GC麼?當然當然是肯定的,那究竟什麼時候我們還需要用到的呢?
1、 排查記憶體溢位
2、 排查記憶體洩漏
3、 效能調優,排查併發瓶頸
我們知道,GC主要處理的是物件的回收操作,那麼什麼時候會觸發一個物件的回收的呢?
1、 物件沒有引用
2、 作用域發生未捕獲異常
3、 程式在作用域正常執行完畢
4、 程式執行了System.exit()
5、 程式發生意外終止(被殺程式等)
其實,我們最容易想到的就是當物件沒有引用的時候會將這個物件標記為可回收物件,那麼現在就有一個問題,是不是這個物件被賦值為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;
}
}
程式的執行結果回事什麼樣子的呢?
我們來看這段程式碼
1、 System.out.println(FinalizedEscapeTestCase.caseForEscape);
2、 FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase();
3、 System.out.println(FinalizedEscapeTestCase.caseForEscape);
4、 FinalizedEscapeTestCase.caseForEscape=null;
5、 System.gc();
6、 Thread.sleep(100);
7、 System.out.println(FinalizedEscapeTestCase.caseForEscape);
1、 當程式執行第一行是,因為這個物件沒有值,結果肯定是null
2、 程式第二行給該物件賦值為新開闢的一個物件
3、 第三行列印的時候,肯定是第二行物件的hash程式碼
4、 第四行將該物件重新置為null
5、 第五行觸發GC
6、 為了保證GC能夠順利執行完畢,第六行等待100毫秒
7、 第七行列印對應的值,回事null麼?一定會是null麼?
我們來看一下對應的執行結果
本例中列印了
GC的日誌,讓我們看的更清晰一點,我們很清晰的看出,最後一句列印的不是null,並且子啊之前,還出現了逃逸的字樣。說明這個物件逃逸了,在垃圾回收之前逃逸了,我們再來看這個pojo的寫法,就會發現,我們重寫了方法finalize,而這個方法就相當於C++中的析構方法,在GC回收之前,會先呼叫一次這個方法,而這個方法又將this指標指向他自己,因此得以成功逃逸!可見,並不是這個物件被賦值為null之後就一定被標記為可回收,有可能會發生逃逸!
下面我們來看一下幾種垃圾收集演算法
1、 在JDK1.2之前,使用的是引用計數器演算法,即當這個類被載入到記憶體以後,就會產生方法區,堆疊、程式計數器等一系列資訊,當建立物件的時候,為這個物件在堆疊空間中分配物件,同時會產生一個引用計數器,同時引用計數器+1,當有新的引用的時候,引用計數器繼續+1,而當其中一個引用銷燬的時候,引用計數器-1,當引用計數器被減為零的時候,標誌著這個物件已經沒有引用了,可以回收了!這種演算法在JDK1.2之前的版本被廣泛使用,但是隨著業務的發展,很快出現了一個問題
當我們的程式碼出現下面的情形時,該演算法將無法適應
a) ObjA.obj = ObjB
b) ObjB.obj - ObjA
這樣的程式碼會產生如下引用情形 objA指向objB,而objB又指向objA,這樣當其他所有的引用都消失了之後,objA和objB還有一個相互的引用,也就是說兩個物件的引用計數器各為1,而實際上這兩個物件都已經沒有額外的引用,已經是垃圾了。
2、 根搜尋演算法
根搜尋演算法是從離散數學中的圖論引入的,程式把所有的引用關係看作一張圖,從一個節點GC ROOT開始,尋找對應的引用節點,找到這個節點以後,繼續尋找這個節點的引用節點,當所有的引用節點尋找完畢之後,剩餘的節點則被認為是沒有被引用到的節點,即無用的節點。
目前java中可作為GC Root的物件有
1、 虛擬機器棧中引用的物件(本地變數表)
2、 方法區中靜態屬性引用的物件
3、 方法區中常量引用的物件
4、 本地方法棧中引用的物件(Native物件)
說了這麼多,其實我們可以看到,所有的垃圾回收機制都是和引用相關的,那我們來具體的來看一下引用的分類,到底有哪些型別的引用?每種引用都是做什麼的呢?
Java中存在四種引用,每種引用如下:
1、 強引用
只要引用存在,垃圾回收器永遠不會回收
Object obj = new Object();
//可直接通過obj取得對應的物件 如obj.equels(new Object());
而這樣 obj物件對後面new Object的一個強引用,只有當obj這個引用被釋放之後,物件才會被釋放掉,這也是我們經常所用到的編碼形式。
2、 軟引用
非必須引用,記憶體溢位之前進行回收,可以通過以下程式碼實現
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
sf.get();//有時候會返回null
這時候sf是對obj的一個軟引用,通過sf.get()方法可以取到這個物件,當然,當這個物件被標記為需要回收的物件時,則返回null;
軟引用主要使用者實現類似快取的功能,在記憶體足夠的情況下直接通過軟引用取值,無需從繁忙的真實來源查詢資料,提升速度;當記憶體不足時,自動刪除這部分快取資料,從真正的來源查詢這些資料。
3、 弱引用
第二次垃圾回收時回收,可以通過如下程式碼實現
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有時候會返回null
wf.isEnQueued();//返回是否被垃圾回收器標記為即將回收的垃圾
弱引用是在第二次垃圾回收時回收,短時間內通過弱引用取對應的資料,可以取到,當執行過第二次垃圾回收時,將返回null。
弱引用主要用於監控物件是否已經被垃圾回收器標記為即將回收的垃圾,可以通過弱引用的isEnQueued方法返回物件是否被垃圾回收器
4、 虛引用(幽靈/幻影引用)
垃圾回收時回收,無法通過引用取到物件值,可以通過如下程式碼實現
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等,這些類頻繁的呼叫自定義類載入器,都需要動態的載入和解除安裝了,以保證永久帶不會溢位,他們通過自定義的類載入器進行了各項操作,因此在實際的應用開發中,類也是被經常載入和解除安裝的,方法區也是會被回收的!但是方法區的回收條件非常苛刻,只有同時滿足以下三個條件才會被回收!
1、所有例項被回收
2、載入該類的ClassLoader被回收
3、Class物件無法通過任何途徑訪問(包括反射)
好了,我們現在切入正題,Java1.2之前主要通過引用計數器來標記是否需要垃圾回收,而1.2之後都使用根搜尋演算法來收集垃圾,而收集後的垃圾是通過什麼演算法來回收的呢?
1、 標記-清除演算法
2、 複製演算法
3、 標記-整理演算法
我們來逐一過一下
1、 標記-清除演算法
標記-清除演算法採用從根集合進行掃描,對存活的物件物件標記,標記完畢後,再掃描整個空間中未被標記的物件,進行回收,如上圖所示。
標記-清除演算法不需要進行物件的移動,並且僅對不存活的物件進行處理,在存活物件比較多的情況下極為高效,但由於標記-清除演算法直接回收不存活的物件,因此會造成記憶體碎片!
2、 複製演算法
複製演算法採用從根集合掃描,並將存活物件複製到一塊新的,沒有使用過的空間中,這種演算法當控制元件存活的物件比較少時,極為高效,但是帶來的成本是需要一塊記憶體交換空間用於進行物件的移動。也就是我們前面提到的
s0 s1等空間。
3、 標記-整理演算法
標記
-整理演算法採用標記-清除演算法一樣的方式進行物件的標記,但在清除時不同,在回收不存活的物件佔用的空間後,會將所有的存活物件往左端空閒空間移動,並更新對應的指標。標記-整理演算法是在標記-清除演算法的基礎上,又進行了物件的移動,因此成本更高,但是卻解決了記憶體碎片的問題。
我們知道,JVM為了優化記憶體的回收,進行了分代回收的方式,對於新生代記憶體的回收(minor GC)主要採用複製演算法,下圖展示了minor GC的執行過程。
對於新生代和舊生代,
JVM可使用很多種垃圾回收器進行垃圾回收,下圖展示了不同生代不通垃圾回收器,其中兩個回收器之間有連線表示這兩個回收器可以同時使用。
而這些垃圾回收器又分為序列回收方式、並行回收方式合併發回收方式執行,分別運用於不同的場景。如下圖所示
下面我們來逐一介紹一下每個垃圾回收器。
1、 Serial收集器
看名字我們都可以看的出來,這個屬於序列收集器。其執行示意圖如下
Serial
收集器是歷史最悠久的一個回收器,JDK1.3之前廣泛使用這個收集器,目前也是ClientVM下 ServerVM 4核4GB以下機器的預設垃圾回收器。序列收集器並不是只能使用一個CPU進行收集,而是當JVM需要進行垃圾回收的時候,需要中斷所有的使用者執行緒,知道它回收結束為止,因此又號稱“Stop The World” 的垃圾回收器。注意,JVM中文名稱為java虛擬機器,因此它就像一臺虛擬的電腦一樣在工作,而其中的每一個執行緒就被認為是JVM的一個處理器,因此大家看到圖中的CPU0、CPU1實際為使用者的執行緒,而不是真正機器的CPU,大家不要誤解哦。
序列回收方式適合低端機器,是Client模式下的預設收集器,對CPU和記憶體的消耗不高,適合使用者互動比較少,後臺任務較多的系統。
Serial收集器預設新舊生代的回收器搭配為Serial+ SerialOld
2、 ParNew收集器
ParNew收集器其實就是多執行緒版本的Serial收集器,其執行示意圖如下
同樣有
Stop The World的問題,他是多CPU模式下的首選回收器(該回收器在單CPU的環境下回收效率遠遠低於Serial收集器,所以一定要注意場景哦),也是Server模式下的預設收集器。
3、 ParallelScavenge
ParallelScavenge又被稱為是吞吐量優先的收集器,器執行示意圖如下
ParallelScavenge
所提到的吞吐量=程式執行時間/(JVM執行回收的時間+程式執行時間),假設程式執行了100分鐘,JVM的垃圾回收佔用1分鐘,那麼吞吐量就是99%。在當今網路告訴發達的今天,良好的響應速度是提升使用者體驗的一個重要指標,多核並行雲端計算的發展要求程式儘可能的使用CPU和記憶體資源,儘快的計算出最終結果,因此在互動不多的雲端,比較適合使用該回收器。
4、 ParallelOld
ParallelOld是老生代並行收集器的一種,使用標記整理演算法、是老生代吞吐量優先的一個收集器。這個收集器是JDK1.6之後剛引入的一款收集器,我們看之前那個圖之間的關聯關係可以看到,早期沒有ParallelOld之前,吞吐量優先的收集器老生代只能使用序列回收收集器,大大的拖累了吞吐量優先的效能,自從JDK1.6之後,才能真正做到較高效率的吞吐量優先。其執行示意圖如下
5、
SerialOld
SerialOld是舊生代Client模式下的預設收集器,單執行緒執行;在JDK1.6之前也是ParallelScvenge回收新生代模式下舊生代的預設收集器,同時也是併發收集器CMS回收失敗後的備用收集器。其執行示意圖如下
6、
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的回收!
7、 GarbageFirst(G1 )
我們再來看垃圾回收器的總圖,剛才我們可以看到,我在圖上標記了一個?,其實這是一個新的垃圾回收器,既可以回收新生代也可以回收舊生代,SunHotSpot 1.6u14以上EarlyAccess版本加入了這個回收器,sun公司預期SunHotSpot1.7釋出正式版,他是商用高效能垃圾回收器,通過重新劃分記憶體區域,整合優化CMS,同時注重吞吐量和響應時間,但是杯具的是被oracle收購之後這個收集器屬於商用收費收集器,因此目前基本上沒有人使用,我們在這裡也就不多介紹,更多資訊可以參考oracle新版本JDK說明。
下面我們再來看下JVM的一些記憶體分配與回收策略
1、 優先在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區域
2、 大物件直接進入老生代
程式碼示例
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日誌,而資料是直接進入老生代的
3、 年長者(長期存活物件)進入老生代
程式碼示例:
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的監控工具可以很清楚的看到物件的年齡,如圖所示
右側的年代數目就是物件的年齡
4、 群體效應(大批中年物件進入老生代)
程式碼示例
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,因此直接進入老生代
5、 擔保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];
}
}
執行結果
1、 無擔保
2、
有擔保
結果分析
我們可以很清楚的看到,當無擔保的時候,觸發了一次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