深刻理解java虛擬機學習筆記-第3章
Table of Contents
1 第3章-垃圾收集器與內存分配策略
1.1 對象已死嗎
1.1.1 引用計數算法
- 測試java不是使用引用計數算法進行垃圾回收的代碼
/** * VM Args: -XX:PrintGCDetails * @author devinkin */ public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 這個成員是屬性的惟一意義就是佔點內存,以便能在GC日誌中清楚看到是否被回收過 */ private byte[] bigSize = new byte[2 * _1MB]; public static void main(String[] args) { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; // 假設在這行發生GC,objA和objB是否能被回收 System.gc(); } }
1.1.2 可達性分析算法
- 可達性分析算法是用來斷定對象是否存活的.
- 可達性分析算法的基本思想就是經過一系列的稱爲"GC Roots"的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時(GC Roots到這個對象不可達),則證實此對象是不可用的.
- Java語言中,可做爲 GC Roots 的對象包括下面幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象.
- 方法區中類靜態屬性引用的對象.
- 方法區中常量引用的對象.
- 本地方法中JNI(即通常說的Native方法)引用的對象.
1.1.3 再談引用
- JDK1.2後,Java將引用分爲: 強引用(Strong Reference),軟引用(Soft Reference),弱引用(Weak Reference),虛引用(Phantom Reference)4種.這四種引用強度依次逐漸減弱.
- 強引用是指在程序代碼之中廣泛存在的,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象.
- 軟引用是用來描述一些還有用但並不是必須的對象.對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中進行第二次回收.若是此次回收尚未足夠的內存,纔會拋出內存溢出異常.
- 弱引用也是用來描述非必須對象的,它的強度比軟引用更弱一點.被弱引用關聯的對象只能生存到下一次垃圾收集發生以前.當垃圾收集器工做時,不管當前內存對象是否足夠,都會回收掉只被弱引用關聯的對象.
- 虛引用是最弱的一種引用關係,一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例.爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知.
1.1.4 生存仍是死亡
- 對象真正死亡,只要要經歷兩次標記過程
- 若是對象在進行可達性分析後發現沒有與"GC Roots"相鏈接的引用鏈,那麼它將會被第一次標記而且進行一次篩選,篩選的條件是此對象是否由必要執行finalize()方法.
當對象沒有覆蓋finalize()方法
,或者finalize()方法已經被虛擬機調用過
,虛擬機將這兩種狀況都視爲"沒有必要執行".- 若是這個對象被斷定爲有必要執行finalize()方法,那麼這個對象將會放置在一個叫作
F-Queue
的隊列之中等待回收. - 稍後
GC
將對F-Queue
中的對象進行第二次小規模標記,若是對象要自救,要在第二次標記時將被移除出"即將回收"的集合.
- 一次對象自我拯救的演示
- 任何一個對象的finalize()方法都只會被系統自動調用一次.
- 若是對象面臨下一次回收,它的finalize()方法不會被再次執行,所以第二段代碼的自救行動失敗了.
/** * 此代碼演示了兩點 * 1. 對象能夠在被GC時自我拯救. * 2. 這種自救的機會只有一次,由於一個對象的finalize()方法最多隻會被系統自動調用一次 * @author devinkin */ public class FinalizeEscapeGC { public static FinalizeEscapeGC SAVE_HOOK = null; public void isAlive() { System.out.println("yes, I am still alive"); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize method executed!"); FinalizeEscapeGC.SAVE_HOOK = this; } public static void main(String[] args) throws InterruptedException { SAVE_HOOK = new FinalizeEscapeGC(); // 對象第一次成功拯救本身 SAVE_HOOK = null; System.gc(); // 由於finalize方法優先級很低,因此暫停0.5秒以等待它. Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no, I am dead :("); } // 下面這段代碼與上面徹底相同,可是此次自救卻失敗了 SAVE_HOOK = null; System.gc(); // 由於finalize方法優先級很低,因此暫停0.5秒以等待它. Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no, I am dead :("); } } }
1.1.5 回收方法區
- 永久代的垃圾收集主要回收兩部份內容: 廢棄常量和無用的類.
- 回收廢棄的常量與回收Java堆中的對象很是相似.
- 類須要同時知足下面3個條件才能算是"無用的類"
- 該類全部的實例都已經被回收,也就是Java堆中不存在該類的任何實例.
- 加載該類的ClassLoader已經被回收.
- 該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法.
- 是否對類進行回收,HotSpot虛擬機提供了
-Xnoclassgc
參數進行控制. - 使用
-verbose:class
以及-XX:+TraceClassLoading
,-XX:+TraceClassUnLoading
查看類加載和卸載信息.
1.2 垃圾收集算法
1.2.1 標記-清除(Mark-Sweep)算法
- 算法分爲"標記"和"清除"兩個階段
- 首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象.
- 標記-清除算法的不足
- 標記和清除兩個過程的效率都不高.
- 標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早出發另外一次垃圾收集動做.
1.2.2 複製算法
- 複製收集算法將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊.
- 當這一塊內存用完了,就將還存活着的對象複製到另一塊上面,而後再把本身使用過的內存空間一次清理掉.
- 每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複製狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效.
- 不足: 將內存縮小爲原來的一半,代價過高.
1.2.3 標記-整理算法
- 標記-整理算法,標記過程依舊同樣,整理過程是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存.
1.2.4 分代收集算法
- 根據對象存活週期的不一樣將內存劃分爲幾塊,通常把Java堆分爲新生代和老年代,根據各個年代的特色採用最適合的收集算法.
- 新生代中,每次垃圾收集時都發現有大批對象死去,只要少許存活,就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集.
- 老年代中由於對象存活率高,沒有額外空間對它進行分配擔保,就必須使用"標記-清理"或者"標記-整理"算法來進行回收.
1.3 HotSpot的算法實現
- 在HotSpot的實現中,使用一組稱爲OopMap的數據結構來記錄哪些地方存放着對象引用.
1.3.1 安全點
- 若是OopMap內容變化的指令很是多,若是爲每一條指令都生成對應的OopMap,須要大量的額外空間,GC成本會變得很高.
- HotSpot在特定的位置記錄了OopMap信息,這些位置稱爲安全點(Safepoint).即程序執行時並不是在全部地方都能停頓下來開始GC,只有達到安全點時才能暫停.
- 安全點的選擇基本上是以程序"是否具備讓程序長時間執行的特徵"爲標準進行選定的.
- "長時間執行"的最明顯特徵就是指令序列服用,例如方法調用,循環跳轉,異常跳轉等,因此具備這些功能的指令纔會產生Safepoint.
- 對於Safepoint,在GC發生時讓全部線程(不包括JNI調用的線程)都在到最近的安全點上停頓的解決方案有2個:
- 搶先式中斷(Preemptive Suspension)
- 主動式中斷(Voluntary Suspension)
- 搶先式中斷不須要線程的執行代碼主動去配合,在GC發生時,首先把全部線程所有中斷,若是發現由線程中斷的地方不在安全點上,就恢復線程,讓"跑"到安全點上.
- 主動式中斷的思想是當GC須要中斷線程的時候,不直接對線程操做,僅僅簡單地設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現中斷標誌爲真時就本身中斷掛起.輪詢標誌的地方和安全點是重合的,另外再加上建立對象須要分配內存的地方.
1.3.2 安全區域
- 安全區域是指在一端代碼片斷中,引用關係不會發生變化.在這個區域中的任意地方開始GC都是安全的.(Safe Region)
1.4 垃圾收集器
- 收集算法是內存回收的方法論,垃圾收集器就是內存回收的具體實現.
1.4.1 Serial收集器
- Serial收集器是單線程的收集器
- Serial收集器單線程的意義不只說明它只會使用一個CPU或一條收集線程去完成收集工做,更重要的是它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束.
1.4.2 ParNew收集器
- ParNew收集器是Serial收集器的多線程版.
- 只有ParNew收集器能與CMS收集器配合工做.
- 可使用
-XX:ParallelGCThreads
參數來限制垃圾收集的線程數. - 收集器並行(Parallcl): 指多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態.
- 收集器併發(Concurrent): 指用戶線程與垃圾收集線程同時執行(但不必定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行於另外一個CPU上.
1.4.3 Parallel Scavenge收集器
- Parrallel Scavenge收集器是一個新生代的收集器,它也是使用複製算法的收集器,又是並行的多線程收集器.
- Parrallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput).所謂吞吐量就是CPU用於運行用戶代碼的時間和CPU總消耗時間的比值.
- 吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間).
- Parrallel Scavenge收集器提供了兩個參數用於精確控制吞吐量
- 控制最大垃圾收集停頓時間的:
--XX:MaxGCPauseMillis
, 收集器將盡量地保證內存回收花費的時間不超過設定值(毫秒). - 直接設置吞吐量大小的:
-XX:GCTimeRatio
, 值是大於0小於100的整數,也就是垃圾收集時間佔總時間的比率,至關於吞吐量的倒數,默認值是99,也就是容許最大1%(即1/(1+99))的垃圾收集時間. - 參數:
-XX:+UseAdaptiveSizePolicy
打開,虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整一些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式稱爲GC自適應的調節策略(GC Ergonomics).
- 控制最大垃圾收集停頓時間的:
1.4.4 Serial Old收集器
- Serial Old是Serial收集器老年代版本,使用"標記-整理"算法.
1.4.5 Parallel Old收集器
- Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和"標記-整理"算法.
1.4.6 CMS收集器
- CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器.
- 適用於重視服務的響應速度,但願系統停頓時間最短的場合.
- CMS收集器是基於"標記-清除"算法,整個過程分爲4個步驟:
- 初始標記(CMS initial mark)
- 併發標記(CMS concurrent mark)
- 從新標記(CMS remark)
- 併發清除(CMS concurrent sweep)
- 初始標記,從新標記這兩個步驟仍然須要"Stop The World".
- 初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快.
- 併發標記階段就是進行GC Roots Tracing的過程.
- 從新標記階段是爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍微長一些,但遠比並發標記的時間短.
- 併發標記和併發清除過程收集器線程和用戶線程一塊兒工做,因此整體來講,CMS收集器的內存回收過程與用戶線程一塊兒併發執行的.
- CMS收集器的優勢
- 併發收集
- 低停頓.
- CMS收集器的缺點
- CMS收集器對CPU資源很是敏感
- CMS收集器沒法處理浮動垃圾,可能出現"Concurrent Mode Failure"失敗而倒置另外一次Full GC的產生.
- 使用"標記-清除"算法,會致使大量的空間碎片產生.
1.4.7 G1收集器
- G1(Garbage-First)收集器是一款面向服務端應用的垃圾收集器.
- G1收集器的特色:
- 並行與併發: G1能充分利用多CPU,多核的硬件優點來縮短Stop-The-World停頓時間.G1收集器仍能夠經過併發的方式讓Java程序繼續執行.
- 分代收集: 能採用不一樣的的方式處理新建立的對象和已經存活了一段時間的舊對象.
- 空間整合: G1從總體來看是基於"標記-整理"算法實現的收集器,從局部(兩個Region之間)來看是基於"複製"算法實現的.G1運做期間都不會產生內存空間碎片.
- 可預測的停頓: 能讓使用者明確指定一個長度爲M毫秒的時間片斷內,消耗在垃圾收集上的時間不得超過N毫秒.
- G1收集器的運做大體可分爲一下幾個步驟
- 初始標記(Initial Marking)
- 併發標記(Concurrent Marking)
- 最終標記(Final Marking)
- 篩選回收(Live Data Counting and Evacuation)
1.4.8 理解GC日誌
- 最前面的數字,表明了GC發生的時間,這個數字的含義是從Java虛擬機啓動以來通過的秒數.
- GC日誌開頭的
[GC
和[Full GC
說明此次垃圾收集的停頓類型,而不是用來區分新生代GC仍是老年代GC.- 若是有
FUll
,說明此次GC是發生了~Stop-The-World~的. - 若是調用
System.gc()
方法所觸發的收集,那麼這裏顯示[Full GC(System)
- 若是有
[DefNew
,[Tenured
,[Perm
表示GC發生的區域.- Serial收集器,新生代名稱爲
[DefNew
- ParNew收集器,新生代名稱爲
[ParNew
- Parallel New Generation收集器,新生代名稱爲
PSYoungGen
, 老年代爲PSOldGen
, 永久代爲PSPermGen
.
- Serial收集器,新生代名稱爲
- 方括號內部的
3324K->152K(3712K)
a含義是"GC前該內存區域已使用容量->GC後該內存區域已使用容量(該內存區域總容量)". - 方括號以外的
3324K->152K(11904K)
表示"GC前Java堆已使用容量->GC後Java堆已使用容量(Java堆總容量)". - 括號以外日後,
0.0025925
表示該內存GC所佔用的時間,單位是秒. - 有的收集器會給出更具體的時間數據,如
[Times: user=0.01, sys=0.00, real=0.02 secs
.- user: 用戶態消耗的CPU時間
- sys: 內核態消耗的CPU時間
- real: 操做從開始到結束所通過的牆鍾時間(Wall Clock Time)
- CPU時間和牆鍾時間的區別
- 牆鍾時間包括各類非運算的等待耗時,例如等待磁盤I/O,等待線程阻塞.
- 系統由多個CPU或者多核,多線程操做會疊加這些CPU時間.user+sys>real很正常.
1.4.9 垃圾收集器參數總結
參數 | 描述 |
---|---|
UseSerialGC | 虛擬機運行在Client模式下的默認值,打開此開關後,使用Serial+Serial Old收集器組合進行內存回收. |
UseParNewGC | 打開次開關後,使用ParNew+Serial Old的收集器組合進行內存回收. |
UseConcMarkSweepGC | 打開此開關後,使用ParNew+CMS+Serial Old的收集器組合進行內存回收.Serial Old收集器將做爲CMS收集器出現Concurrent Mode Failure失敗後的後備收集器使用. |
UseParallelGC | 虛擬機運行在Server模式下的默認值,打開此開關後,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器組合進行內存回收. |
SurvivoRatio | 新生代中Eden區域與Survivor區域的容量比值,默認是8,表明Eden:Suvivor=8:1. |
PretenureSizeThreshold | 直接晉升到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配. |
MaxTenuringThreshold | 晉升到老年代的對象年齡,每一個對象在堅持過一次Minor GC以後,年齡就增長1,當超過這個參數值時就進入老年代. |
UseAdaptiveSizePolicy | 動態調整Java堆中各個區域的大小以及進入老年代的年齡 |
HandlePromotionFailure | 是否容許分配擔保失敗,即進入老年代的剩餘空間不足以應付新生代的整個Eden和Survivor區的全部對象都存活的極端狀況. |
ParallelGCThreads | 設置並行GC時進行內存回收的線程數 |
GCTimeRatio | GC時間佔總時間的比率,默認是99,即容許1%的GC時間,僅在使用Parallel Scavenge收集器時生效. |
MaxGCPauseMillis | 設置GC的最大停頓時間,僅在使用Parallel Scavenge收集器時生效. |
CMSInitiatingOccupancyFraction | 設置CMS收集器在老年代空間被使用多少後出發垃圾收集,默認值爲爲68%,僅在使用CMS收集器時生效. |
UseCMSCompactAtFullCollection | 設置CMS收集器在完成垃圾收集後是否要進行一次內存碎片管理,僅在CMS收集器時生效. |
CMSFullGCBeforeCompaction | 設置CMS收集器在進行若干次垃圾收集後再啓動一次內存碎片管理,僅在使用CMS收集器時生效. |
1.5 內存分配與回收策略
- 對象的內存分配,往大方向講,就是在堆上分配,對象主要分配在新生代的Eden區上,若是啓動了本地線程分配緩衝,將按線程優先在TLAB上分配.
1.5.1 對象優先在Eden分配
- 大多狀況下,對象在新生代Eden區中分配,當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC.
- 虛擬機提供了
-XX:+PrintGCDetails
這個收集器日誌參數,告訴虛擬機在垃圾收集行爲時打印內存回收日誌,而且在進程退出的時候輸出當前的內存各區域分配狀況. - Minor GC 和 FULL GC 有什麼不同?
- 新生代GC(Minor GC): 指發生在新生代的垃圾收集動做,由於Java對象大多都具有朝生夕滅的特性,因此Minor GC很是頻繁,通常回收速度也比較快.
- 老年代GC(Major GC/Full GC): 指發生在老年代的GC,出現了Major GC,常常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略裏就有直接進行Major GC的策略選擇過程).Major GC的速度通常會比Minor GC慢10倍以上.
- 新生代Minor GC
/** * 內存分配與回收策略 * @author devinkin */ public class AllocateGC { private static final int _1MB = 1024 * 1024; /** * VM 參數: -XX:+UseSerialGC -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 */ public static void testAllocation() { byte[] allocation1, allocation2, allocation3, allocation4; // 6M,eden區對象沒有被垃圾回收成功,Minor GC時候,就把這6M轉移到空的Survivor區域 // SUrvivor由from to兩個 // 但Survivor只有1M,GC期間虛擬機又發現已有的3個2MB大小對象所有沒法放入Survivor空間 // 因此只好經過分配擔保機制提早轉移到老年區(tenured generation) allocation1 = new byte[2 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; // 出現一次Minor GC,年輕代到達了10M allocation4 = new byte[4 * _1MB]; } public static void main(String[] args) { testAllocation(); } }
1.5.2 大對象直接進入老年代
- 大對象就是大量連續內存空間的Java對象,例如很長的字符串以及數組.
- 常常出現大對象容易致使內存還有很多空間時就提早出發垃圾收集以獲取足夠的連續空間來"安置"它們.
- 虛擬機提供了一個
-XX:PretenureSizeThreshold
參數,令大於這個設置值的對象直接在老年代分配.- 目的: 避免在Eden區及兩個Survivor區之間發生大量的內存複製.(新生代採用複製算法收集內存).
- 大對象直接進入老年代
/** * 大對象直接進入老年代 */ public class AllocateGC2 { private static final int _1MB = 1024 * 1024; /** * VM參數: -XX:+UseSerialGC -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728 */ public static void testPretenureSizeThreshold() { byte[] allocation; // 直接分配在老年代 allocation = new byte[4 * _1MB]; } public static void main(String[] args) { testPretenureSizeThreshold(); } }
1.5.3 長期存活的對象將進入老年代
- 虛擬機給每一個對象定義了一個對象年齡(Age)計數器.若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納的話,將被移動到Survivor空間中,並將對象年齡設爲1.
- 對象在Survivor區中每"熬過"一次Minor GC,年齡就會增長1歲,當它的年齡增長到必定程度(默認是15歲),就會被晉升到老年代中.
- 對象晉升老年代的年齡閥值,能夠經過參數
-XX:MaxTenuringThreshold
設置. - 長期存活的對象將進入老年代
/** * 長期存活的對象進入老年代 */ public class AllocateGC3 { private static final int _1MB = 1024 * 1024; /** * VM參數: -XX:+UseSerialGC -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution */ @SuppressWarnings("unused") public static void testTenuringThreshold() { byte[] allocation1, allocation2, allocation3; allocation1 = new byte[_1MB / 4]; // 何時進入老年代取決於XX:MaxTenuringThreshold設置 allocation2 = new byte[4 * _1MB]; allocation3 = new byte[4 * _1MB]; // Minor GC,年齡爲allocation1,2,3都被移動到Survivor空間,年齡爲1 //因爲年齡爲1,因此allocation1,2,3直接晉升爲老年代,移動到tenured gen空間中 allocation3 = null; allocation3 = new byte[4 * _1MB]; // 這是第二次minor GC } public static void main(String[] args) { testTenuringThreshold(); } }
1.5.4 動態對象年齡判斷
- 虛擬機並非永遠地要求對象的年齡必須達到了
MaxTenuringThreshold
才能晉升爲老年代. - 若是在Survivor空間中相同年齡的全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡.
- 動態對象年齡判斷
/** * 動態對象年齡斷定 */ public class AllocateGC4 { private static final int _1MB = 1024 * 1024; /** * VM 參數: * -XX:+UseSerialGC * -verbose:gc * -Xms20M * -Xmx20M * -Xmn10M * -XX:+PrintGCDetails * -XX:SurvivorRatio=8 * -XX:MaxTenuringThreshold=15 * -XX:+PrintTenuringDistribution */ public static void testTenuringThreeshold2() { byte[] allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[_1MB / 4]; // allocation1 + allocation2大於survivor空間一半 allocation2 = new byte[_1MB / 4]; allocation3 = new byte[4 * _1MB]; // 第一次Minor GC allocation4 = new byte[4 * _1MB]; allocation4 = null; // 第二次Minor GC allocation4 = new byte[4 * _1MB]; } public static void main(String[] args) { testTenuringThreeshold2(); } }
1.5.5 空間分配擔保
- 在發生Minor GC以前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代全部對象總空間.
- 若是這個條件成立,那麼Minor GC能夠確保是安全的.
- 若是這個條件不成立,則虛擬機會查看HandlePromotionFailure設置值是否容許擔保失敗.
- 若是容許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小.若是大於,將嘗試進行一次Minor GC.若是小於,或者HandlePromotionFailure設置不容許擔保失敗,那這時也要改成進行一次Full GC.
- 當出現大量對象在Minor GC後仍然存活的狀況(最極端的狀況就是內存回收後新生代中全部對象都存活),就須要老年代進行分配擔保,把Survivor沒法容納的對象直接進入老年代.
- 前提是老年代自己還有容納這些對象的剩餘空間,因此只好取每一次回收晉升到老年代對象容量的平均大小值做爲經驗值,與老年代的剩餘空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間.
- 空間分配擔保
/** * 空間分配擔保 * * @author devinkin */ public class AllocateGC5 { private static final int _1MB = 1024 * 1024; /** * VM參數: * -XX:UseSerialGC * -Xms20M * -Xmx20M * -Xmn10M * -XX:+PrintGCDetails * -XX:SurvivorRatio=8 * -XX:+HandlePromotionFailure */ public static void testHandlePromotion() { byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7; allocation1 = new byte[2 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; // 回收allocation1進入了老年代,由於Survivor內存爲1M,不足以存放2M的內容 allocation1 = null; // 第一次Minor GC,回收了allocation1 allocation4 = new byte[2 * _1MB]; // 第二次Minor GC,allocation5直接進入老年代 allocation5 = new byte[2 * _1MB]; // 第三次Minor GC,allocation6直接進入老年代 allocation6 = new byte[2 * _1MB]; allocation4 = null; allocation5 = null; allocation6 = null; // 在老年代分配allocation7,可能會致使Full GC的進行 allocation7 = new byte[2 * _1MB]; } public static void main(String[] args) { testHandlePromotion(); } }