Java與C++之間有一堵由內存動態分配和垃圾收集技術所圍成的「高牆」,牆外的人想進來,牆裏面的人卻想出來。
原理:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值加1;當引用失效時,計數器減1,任什麼時候刻計數器都爲0的對象就是不可能再被使用的。
優勢:實現原理簡單,並且斷定效率很高。
缺點:很難解決對象之間相互循環引用的問題。html
原理:經過一系列名爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain)。當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。java
不管是經過引用計數算法判斷對象的引用數量,仍是經過根搜索算法判斷對象的引用鏈是否可達,斷定對象是否存活都與「引用」有關。算法
在JDK1.2以前,Java中的引用的定義很傳統:若是reference類型的數據中存儲的數值表明的是另一塊內存的起始地址,就稱這塊內存表明着一個引用。
缺點:一個對象在這種定義下只有被引用或者沒有被引用兩種狀態,咱們但願能描述這樣一類對象——當內存空間還足夠時,則能保留在內存之中;若是內存在GC以後仍是很是緊張,則能夠拋棄這些對象(如緩存)。數組
在 JDK 1.2 以後,Java對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference),這四種引用強度依次逐漸減弱。緩存
更多資料:深刻探討 java.lang.ref 包、慎用java.lang.ref.SoftReference實現緩存安全
即便在可達性分析算法中不可達的對象,也並不是是「非死不可」的,這時候它們暫時處於「緩刑」階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:多線程
finalize()
方法。當對象沒有覆蓋finalize方法,或者finalize()
方法已經被虛擬機調用過,虛擬機將這兩種狀況都視爲「沒有必要執行」,對象被回收。finalize()
方法,那麼這個對象將會被放置在一個名爲F-Queue
的隊列之中,並在稍後由一條虛擬機自動創建的、低優先級的Finalizer線程去執行。這裏所謂的「執行」是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束。這樣作的緣由是,若是一個對象finalize()
方法中執行緩慢,或者發生死循環,將極可能會致使F-Queue隊列中的其餘對象永久處於等待狀態,甚至致使整個內存回收系統崩潰。Finalize()
方法是對象脫逃死亡命運的最後一次機會,稍後GC將對F-Queue
中的對象進行第二次小規模標記。若是對象要在Finalize()
中成功拯救本身——只要從新與引用鏈上的任何的一個對象創建關聯便可,那在第二次標記時它將移除出「即將回收」的集合。若是對象這時候還沒逃脫,那基本上它就真的被回收了。public class FinalizeEscapeGC { public static FinalizeEscapeGC SAVE_HOOK = null; public void isAlive() { System.out.println("yes, I am still alive"); } 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 QAQ!"); } // 以上代碼與上面的徹底相同,但此次自救卻失敗了!!! 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 QAQ!"); } } }
System.gc()
底層調用的是Runtime.getRuntime().gc();
,該方法的Java doc裏邊寫的是調用此方法suggests
JVM進行GC,即沒法保證對垃圾收集器的調用。finalize()
方法至多由GC執行一次,用戶固然能夠手動調用對象的finalize方法,但並不影響GC對finalize()
的行爲。finalize()
方法完成不少操做如關閉外部資源,但更好的方式應該是try-finally
。finalize()
運行代價高昂,不肯定大,沒法保證各個對象的調用順序。Java虛擬機規範不要求虛擬機在方法區實現垃圾收集;方法區的GC性價比通常比較低。
方法區的GC主要是回收兩部份內容:廢棄常量和無用的類。併發
判斷常量是否廢棄跟對象是同樣。常量池中的其餘類、接口、方法、字段的符號引用也是如此。框架
-Xnoclassgc
參數進行控制,還可使用-verbose:class
以及-XX:+TraceClassLoading
、-XX:+TraceClassUnLoading
查看類加載和卸載信息,其中-verbose:class
和-XX:+TraceClassLoading
能夠在Product版的虛擬機中使用,-XX:+TraceClassUnLoading
參數須要FastDebug版的虛擬機支持。定義:標記-清除(Mark-Sweep)算法分爲標記和清除兩個階段,首先標記出須要回收的對象,標記完成以後統一清除對象。
缺點:效率問題,標記和清除過程效率不高;標記清除以後會產生大量不連續的內存碎片。ide
定義:複製(Copying)算法它將可用內存容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊用完以後,就將還存活的對象複製到另一塊上面,而後在把已使用過的內存空間一次理掉。
優勢:這樣使得每次都是對其中的一塊進行內存回收,不會產生碎片等狀況,只要移動堆訂的指針,按順序分配內存便可,實現簡單,運行高效。
缺點:內存縮小爲原來的一半。
使用狀況:如今的商業虛擬機都採用這種收集算法來回收新生代,新生代中的對象98%都是「朝生夕死」的,因此並不須要按照1:1的比例來劃份內存空間,而是將內存分爲一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活着的對象一次性地複製到另一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。
HotSpot虛擬機:默認Eden和Survivor的大小比例是8:1,也就是說,每次新生代中可用內存空間爲整個新生代容量的90%(80%+10%),只有10%的空間會被浪費。
定義:標記-整理算法的標記過程與標記清除算法同樣,但後續步驟不是直接對可回收對象進行清理,而是對全部存活的對象都向一端移動,而後清理掉邊界之外的內存。
優勢:解決了複製算法在對象存活率較高狀況下須要大量複製致使的效率問題,並且不會縮小內存。
定義:根據對象存活週期的不一樣將內存分爲幾塊,通常是把Java堆分爲新生代和老年代,根據各個年代的特色採用最適用的算法。
新生代:每次收集都會有大批對象死去,只有少許存活,採用複製算法。
老年代:對象存活率較高、沒有額外空間對它進行分配擔保,採用標記-清除或標記-整理算法。
可達性分析的效率問題:可做爲GC Roots的節點主要在全局性的引用(常量或類的靜態屬性)與執行上下文(如棧幀的本地變量表)中,如今不少應用僅僅方法區就有數百兆,若是逐個檢查引用必然會消耗不少時間。
GC停頓:可達性分析在分析期間整個執行系統看起來就像被凍結在某個時間點上,不能夠出現分析過程當中對象引用關係還在不斷變化的狀況,這就是致使GC進行時必須停頓全部Java執行線程(Sun將這件事情成爲「Stop The World」)的一個重要緣由,即便在號稱(幾乎)不會發生停頓的CMS收集器中,枚舉跟結點也是必需要暫停的。
準確是GC:主流JVM都使用的是準確式GC,即JVM知道內存中某位置的數據類型什麼,因此當執行系統停下來的時候,不須要一個不漏的檢查完全部執行上下文和全局的引用位置,虛擬機能夠有辦法知道哪些地方存放着對象的引用。
HotSpot的OOPMap:在類加載完成的時候,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來;在JIT編譯過程當中,也會在特定的位置記錄下棧和寄存器中哪些位置是引用。這樣GC在掃描的時候就能夠直接得到這些信息。
爲何須要安全點:有了OOPMap,HotSpot能夠快而準的完成GC Roots的查找,但若是爲每一行代碼的指令都生成OOPMap,這樣將佔用大量的空間。因此HotSpot並無這麼作!
安全點:HotSpot只在特定的位置記錄了OOPMap,這些位置稱爲安全點(Safe Point),即程序不能在任意地方均可以停下來進行GC,只有到達安全點時才能暫停進行GC。
安全點的選定基本上是以「是否具備讓程序長時間執行的特徵」進行選定的,既不能選擇太少以至於讓GC等待過久,與不能太頻繁以至於增大系統負荷。具體的安全點有:
安全點的不足:安全點機制保證了程序執行時,在較短的時間就會遇到能夠進入GC的安全點,但若是程序處於不執行狀態(如Sleep狀態或者Blocked狀態),這時候線程沒法相應JVM的中斷請求,沒法運行到安全點去中斷掛起,JVM也不會等待線程從新被分配CPU時間。
安全區域:安全區域(Safe Region)是指在一段代碼片斷之中,引用關係不會發生變化,這個區域的任何地方GC都是安全的。能夠把安全區域當作是擴展了的安全點。
這裏討論的收集器基於JDK 7 Update14的HotSpot虛擬機,這個版本中正式提供了商用的G1收集器。下圖展現了HotSpot虛擬機的垃圾收集器,若是兩個收集器存在連線,說明能夠搭配使用。
簡介:最基本、最悠久、單線程
缺點:只會使用一條線程完成GC工做,並且在工做時必須暫停其餘全部工做線程。
優勢:簡單而高效(與其餘收集器的單線程比),是JVM運行在Client模式下的默認新生代收集器。
-XX:+UseSerialGC
,設置以後默認使用Serial(年輕代)+Serial Old(老年代) 組合進行GC。
簡介:Serial的多線程版本,其他行爲包括Serial的全部控制參數、收集算法、Stop The World、對象分配規則、回收策略等都與Serial徹底同樣,默認開啓的收集線程數與CPU數量相同。
優勢:多線程收集、能與CMS配合工做(這也是它是許多Server模式下虛擬機中首選的緣由)
缺點:單線程效率不及Serial。
-XX:+UseConcMarkSweepGC
的默認收集器-XX:+UseConcMarkSweepGC
強制指定-XX:ParallelGCThreads
參數來限制垃圾收集的線程數。簡介:新生代收集器、採用複製算法、並行多線程收集器、關注的目標是達到一個可控制的吞吐量而非儘量的縮短GC時用戶線程的停頓時間。
吞吐量:CPU用於運行用戶代碼的時間和CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)
。停頓時間越短適合與用戶交互的程序,良好的相應速度能提高用戶體驗;而高吞吐量能夠高效利用CPU時間,適合後臺運算。
-XX:MaxGCPauseMillis
:控制最大垃圾收集停頓時間,是一個大於0的毫秒數-XX:GCTimeRatio
:直接設置吞吐量大小,是一個大於0且小於100的整數,默認值是99,就是容許最大1%即(1/(1+99)
)的垃圾收集時間。-XX:+UseAdaptiveSizePolicy
:若是設置此參數,就不須要手工設定新生代的大小、Eden於Survivor區的比例、晉升老年代對象年齡等細節參數來,虛擬機會動態調整。簡介:Serial的老年代版本、單線程、使用標記整理算法
用途:主要是爲Client模式下的虛擬機使用;在Server模式下有兩大用途,一是在JDK 5及以前版本中配合Parallel Scavenge收集器一塊兒使用,而是做爲CMS的後備預案,在併發收集發生Concurrent Mode Failure時使用。
簡介:Parallel Scavenge的老年代版本、多線程、標記整理算法、JDK 6中才出現
用途:直到Parallel Old收集器出現後,「吞吐量優先」收集器終於有了比較名副其實的應用組合,在注重吞吐量以及CPU資源敏感的場合,可使用Parallel Scavenge和Parallel Old的組合。
簡介:CMS(Concurrent Mark Sweep)以最短回收停頓時間爲目標、適合B/S系統的服務端、基於標記清除算法
優勢:併發收集、低停頓
若是不計算維護Remembered Set的操做,G1收集器的運做大體可劃分爲如下幾個步驟:
參數 | 描述 |
---|---|
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) 的收集器組合進行內存回收 |
UseParallelOldGC | 打開此開關後,使用 Parallel Scavenge + Parallel Old 的收集器組合進行內存回收 |
SurvivorRatio | 新生代中 Eden 區域與 Survivor 區域的容量比值,默認爲8,表明 Eden : Survivor = 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 收集器時生效 |
CMSFullGCsBeforeCompaction | 設置 CMS 收集器在進行若干次垃圾收集後再啓動一次內存碎片整理,僅在使用 CMS 收集器時生效 |
每一種收集器的日誌形式都是由它們自身的實現所決定的,換言之每一個收集器的日誌格式均可以不同。但虛擬機設計者爲了方便用戶閱讀,將各個收集器的日誌都維持必定的共性,例如如下兩段典型的GC日誌:
33.125:[GC[DefNew:3324K->152K(3712K),0.0025925secs]3324K->152K(11904K),0.0031680 secs] 100.667:[FullGC[Tenured:0K->210K(10240K),0.0149142secs]4603K->210K(19456K),[Perm:2999K->2999K(21248K)],0.0150007 secs][Times:user=0.01 sys=0.00,real=0.02 secs]
對象的內存分配總的來講,就是在堆上分配(但也可能通過JIT編譯後被拆散爲標量類型並間接地棧上分配);對象主要分配在新生代的Eden區上;若是啓動了本地線程分配緩衝,將按線程優先在TLAB上分配;少數狀況下也可能會直接分配在老年代中。分配的規則並非百分之百固定的,其細節取決於當前使用的是哪種垃圾收集器組合,還有虛擬機中與內存相關的參數的設置。
大多數狀況下,對象在新生代Eden區中分配。當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。
/** * -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 */ public class Allocation { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[2 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation4 = new byte[4 * _1MB]; // Minor GC } }
[GC (Allocation Failure) [DefNew: 7482K->380K(9216K), 0.0061982 secs] 7482K->6524K(19456K), 0.0062260 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] Heap def new generation total 9216K, used 4641K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 52% used [0x00000007bec00000, 0x00000007bf0290f0, 0x00000007bf400000) from space 1024K, 37% used [0x00000007bf500000, 0x00000007bf55f318, 0x00000007bf600000) to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) tenured generation total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000) Metaspace used 2968K, capacity 4496K, committed 4864K, reserved 1056768K class space used 327K, capacity 388K, committed 512K, reserved 1048576K
什麼是大對象:大對象就是指須要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串及數組(byte[]數組就是典型的大對象)。
大對象的影響:大對象對虛擬機的內存分配來講就是一個壞消息(更加壞的狀況就是遇到一羣朝生夕死的短命 對象,寫程序時應該避免),常常出現大對象容易致使內存還有很多空間時就提早觸發垃圾收集以獲取足夠的連續空間來安置大對象。
設置大對象的參數:能夠經過-XX:PretenureSizeThreshold
參數設置使得大於這個設置值的對象直接在老年代分配,避免在Eden區及兩個Survivor區之間發生大量的內存拷貝。
/** * -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728(3M) */ public class PretenureSizeThreshold { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation = new byte[4 * _1MB]; } }
Heap def new generation total 9216K, used 1502K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 18% used [0x00000007bec00000, 0x00000007bed778d8, 0x00000007bf400000) from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) tenured generation total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000) Metaspace used 2931K, capacity 4496K, committed 4864K, reserved 1056768K class space used 321K, capacity 388K, committed 512K, reserved 1048576K
對象年齡:虛擬機給每一個對象定義了一個對象年齡(Age)計數器。若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納的話,將被移動到Survivor空間中,而且對象年齡設爲1。對象在Survivor區中每「熬過」一次Minor GC,年齡就增長1歲,當它的年齡增長到必定程度(默認爲15歲),就將會被晉升到老年代中。
設置對象晉升年齡:經過參數-XX:MaxTenuringThreshold
來設置。
/** * -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 */ public class MaxTenuringThreshold { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation1, allocation2, allocation3; allocation1 = new byte[_1MB / 4]; allocation2 = new byte[4 * _1MB]; allocation3 = new byte[4 * _1MB]; // Eden空間不足GC,allocation1進入Survivor allocation3 = null; allocation3 = new byte[4 * _1MB]; // Eden空間不足第二次GC } }
[GC (Allocation Failure) [DefNew: 5690K->624K(9216K), 0.0052742 secs] 5690K->4720K(19456K), 0.0053049 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] [GC (Allocation Failure) [DefNew: 4720K->0K(9216K), 0.0009947 secs] 8816K->4709K(19456K), 0.0010106 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4260K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 52% used [0x00000007bec00000, 0x00000007bf0290f0, 0x00000007bf400000) from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) tenured generation total 10240K, used 4709K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 45% used [0x00000007bf600000, 0x00000007bfa99570, 0x00000007bfa99600, 0x00000007c0000000) Metaspace used 2953K, capacity 4496K, committed 4864K, reserved 1056768K class space used 327K, capacity 388K, committed 512K, reserved 1048576K
此方法中allocation1對象須要256KB的內存空間,Survivor空間能夠容納。當MaxTenuringThreshold=1時,allocation1對象在第二次GC發生時進入老年代,新生代已使用的內存GC後會很是乾淨地變成0KB。而 MaxTenuringThreshold=15時,第二次GC發生後,allocation1對象則還留在新生代Survivor空間,這時候新生代仍然有410KB的空間被佔用。
爲了能更好地適應不一樣程序的內存情況,虛擬機並不老是要求對象的年齡必須達到MaxTenuringThreshold才能晉升到老年代,若是在 Survivor空間中相同年齡全部對象大小的綜合大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
/** * -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 */ public class Main { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[_1MB / 4]; allocation2 = new byte[_1MB / 4]; allocation3 = new byte[4 * _1MB]; allocation4 = new byte[4 * _1MB]; // 第一次GC allocation4 = null; allocation4 = new byte[4 * _1MB]; // 第二次GC } }
[GC (Allocation Failure) [DefNew: 5946K->880K(9216K), 0.0045988 secs] 5946K->4976K(19456K), 0.0046307 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [DefNew: 5058K->0K(9216K), 0.0012867 secs] 9154K->4965K(19456K), 0.0013125 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4315K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 52% used [0x00000007bec00000, 0x00000007bf036ce8, 0x00000007bf400000) from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf4000e0, 0x00000007bf500000) to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) tenured generation total 10240K, used 4965K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 48% used [0x00000007bf600000, 0x00000007bfad9500, 0x00000007bfad9600, 0x00000007c0000000) Metaspace used 2957K, capacity 4496K, committed 4864K, reserved 1056768K class space used 327K, capacity 388K, committed 512K, reserved 1048576K
發現運行結果中Survivor佔用仍然爲0%,而老年代比預期增長了,也就是說allocation1,allocation2對象都直接進入了老年代,而沒有等到15歲的臨界年齡。由於這兩個對象加起來達到了512KB,而且它們是同年的,知足同年對象達到Survivor空間的一半規則。 咱們只要註釋一個對象的new操做,就會發現另一個不會晉升到老年代了。
JDK 6 Update 24以後,HandlePromotionFailure參數不會再影響到虛擬機的空間分配擔保策略,只要老年代的連續空間大於新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC,不然將進行Full GC。