1. 程式人生 > >JVM之記憶體分配與回收策略

JVM之記憶體分配與回收策略

JVM之記憶體分配與回收策略

來源 https://www.cnblogs.com/xiaoxi/p/6557473.html

 

JVM分代垃圾回收策略的基礎概念

來源 https://www.cnblogs.com/xiaoxi/p/6602166.html

一、為什麼要分代

     分代的垃圾回收策略,是基於這樣一個事實:不同的物件的生命週期是不一樣的。因此,不同生命週期的物件可以採取不同的收集方式,以便提高回收效率。

     在Java程式執行的過程中,會產生大量的物件,其中有些物件是與業務資訊相關,比如Http請求中的Session物件、執行緒、Socket連線,這類物件跟業務直接掛鉤,因此生命週期比較長。但是還有一些物件,主要是程式執行過程中生成的臨時變數,這些物件生命週期會比較短,比如:String物件,由於其不變類的特性,系統會產生大量的這些物件,有些物件甚至只用一次即可回收。

     試想,在不進行物件存活時間區分的情況下,每次垃圾回收都是對整個堆空間進行回收,花費時間相對會長,同時,因為每次回收都需要遍歷所有存活物件,但實際上,對於生命週期長的物件而言,這種遍歷是沒有效果的,因為可能進行了很多次遍歷,但是他們依舊存在。因此,分代垃圾回收採用分治的思想,進行代的劃分,把不同生命週期的物件放在不同代上,不同代上採用最適合它的垃圾回收方式進行回收。

二、如何分代

如圖所示:

     虛擬機器中的共劃分為三個代:年輕代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類資訊,與垃圾收集要收集的Java物件關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。

年輕代:

     所有新生成的物件首先都是放在年輕代的。年輕代的目標就是儘可能快速的收集掉那些生命週期短的物件。年輕代分三個區。一個Eden區,兩個Survivor區(一般而言)。大部分物件在Eden區中生成。當Eden區滿時,還存活的物件將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活物件將被複制到另外一個Survivor區,當這個Survivor區也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的物件,將被複制“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來的物件,和從前一個Survivor複製過來的物件,而複製到年老區的只有從第一個Survivor區過來的物件。而且,Survivor區總有一個是空的。同時,根據程式需要,Survivor區是可以配置為多個的(多於兩個),這樣可以增加物件在年輕代中的存在時間,減少被放到年老代的可能。

     新生代有劃分為Eden、From Survivor和To Survivor三個部分,他們對應的記憶體空間的大小比例為8:1:1,也就是,為物件分配記憶體的時候,首先使用Eden空間,經過GC後,沒有被回收的會首先進入From Survivor區域,任何時候,都會保持一個Survivorq區域(From Survivor或To Survivor)完全空閒,也就是說新生代的記憶體利用率最大為90%。From Survivor和To Survivor兩個區域會根據GC的實際情況,進行互換,將From Survivor區域中的物件全部複製到To Survivor區域中,或者反過來,將To Survivor區域中的物件全部複製到From Survivor區域中。

年老代:

     在年輕代中經歷了N次垃圾回收後仍然存活的物件,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命週期較長的物件。

     GC過程中,當某些物件經過多次GC都沒有被回收,可能會進入到年老代。或者,當新生代沒有足夠的空間來為物件分配記憶體時,可能會直接在年老代進行分配。

持久代:

     用於存放靜態檔案,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者呼叫一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些執行過程中新增的類。持久代大小通過-XX:MaxPermSize=<N>進行設定。

    永久代實際上對應著虛擬機器執行時資料區的“方法區”,這裡主要存放類資訊、靜態變數、常量等資料。一般情況下,永久代中對應的物件的GC效率非常低,因為這裡的的大部分物件在執行都不要進行GC,它們會一直被利用,直到JVM退出。

三、什麼情況下觸發垃圾回收

由於物件進行了分代處理,因此垃圾回收區域、時間也不一樣。GC有兩種型別:Scavenge GC和Full GC

Scavenge GC

    一般情況下,當新物件生成,並且在Eden申請空間失敗時,就會觸發Scavenge GC,對Eden區域進行GC,清除非存活物件,並且把尚且存活的物件移動到Survivor區。然後整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。因為大部分物件都是從Eden區開始的,同時Eden區不會分配的很大,所以Eden區的GC會頻繁進行。因而,一般在這裡需要使用速度快、效率高的演算法,使Eden去能儘快空閒出來。

     對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個塊進行回收,所以比Scavenge GC要慢,因此應該儘可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對於FullGC的調節。有如下原因可能導致Full GC:

· 年老代(Tenured)被寫滿

· 持久代(Perm)被寫滿

· System.gc()被顯示呼叫

·上一次GC之後Heap的各域分配策略動態變化

 

------------------------------ Next

 

前言

     物件的記憶體分配,往大的方向上講,就是在堆上分配,少數情況下也可能會直接分配在老年代中,分配的規則並不是百分之百固定的,其細節決定於當前使用的是哪種垃圾收集器組合,當然還有虛擬機器中與記憶體相關的引數。垃圾收集器組合一般就是Serial+Serial Old和Parallel+Serial Old,前者是Client模式下的預設垃圾收集器組合,後者是Server模式下的預設垃圾收集器組合,文章使用對比學習法對比Client模式下和Server模式下同一條物件分配原則有什麼區別。

TLAB

     首先講講什麼是TLAB。記憶體分配的動作,可以按照執行緒劃分在不同的空間之中進行,即每個執行緒在Java堆中預先分配一小塊記憶體,稱為本地執行緒分配緩衝(Thread Local Allocation Buffer,TLAB)。哪個執行緒需要分配記憶體,就在哪個執行緒的TLAB上分配。虛擬機器是否使用TLAB,可以通過-XX:+/-UseTLAB引數來設定。這麼做的目的之一,也是為了併發建立一個物件時,保證建立物件的執行緒安全性。TLAB比較小,直接在TLAB上分配記憶體的方式稱為快速分配方式,而TLAB大小不夠,導致記憶體被分配在Eden區的記憶體分配方式稱為慢速分配方式。

一、物件優先在Eden區分配

物件通常在新生代的Eden區進行分配,當Eden區沒有足夠空間進行分配時,虛擬機器將發起一次Minor GC,與Minor GC對應的是Major GC、Full GC。

Minor GC:指發生在新生代的垃圾收集動作,非常頻繁,速度較快。

Major GC:指發生在老年代的GC,出現Major GC,經常會伴隨一次Minor GC,同時Minor GC也會引起Major GC,一般在GC日誌中統稱為GC,不頻繁。

Full GC:指發生在老年代和新生代的GC,速度很慢,需要Stop The World。

來看下面一段程式碼,虛擬機器引數為“-verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8”,即10M新生代,10M老年代,10M新生代中8M的Eden區,兩個Survivor區各1M。

public class EdenAllocationTest
{
    private static final int _1MB = 1024 * 1024;
    
    public static void main(String[] args)
    {
        byte[] allocation1 = new byte[2 * _1MB];
        byte[] allocation2 = new byte[2 * _1MB];
        byte[] allocation3 = new byte[2 * _1MB];
        byte[] allocation4 = new byte[4 * _1MB];
    }
}

Client模式下:

[GC [DefNew: 6487K->194K(9216K), 0.0042856 secs] 6487K->6338K(19456K), 0.0043281 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4454K [0x0000000005180000, 0x0000000005b80000, 0x0000000005b80000)
  eden space 8192K,  52% used [0x0000000005180000, 0x00000000055a9018, 0x0000000005980000)
  from space 1024K,  18% used [0x0000000005a80000, 0x0000000005ab0810, 0x0000000005b80000)
  to   space 1024K,   0% used [0x0000000005980000, 0x0000000005980000, 0x0000000005a80000)
 tenured generation   total 10240K, used 6144K [0x0000000005b80000, 0x0000000006580000, 0x0000000006580000)
   the space 10240K,  60% used [0x0000000005b80000, 0x0000000006180048, 0x0000000006180200, 0x0000000006580000)
 compacting perm gen  total 21248K, used 2982K [0x0000000006580000, 0x0000000007a40000, 0x000000000b980000)
   the space 21248K,  14% used [0x0000000006580000, 0x0000000006869890, 0x0000000006869a00, 0x0000000007a40000)
No shared spaces configured.

Server模式下:

Heap
 PSYoungGen      total 9216K, used 6651K [0x000000000af20000, 0x000000000b920000, 0x000000000b920000)
  eden space 8192K, 81% used [0x000000000af20000,0x000000000b59ef70,0x000000000b720000)
  from space 1024K, 0% used [0x000000000b820000,0x000000000b820000,0x000000000b920000)
  to   space 1024K, 0% used [0x000000000b720000,0x000000000b720000,0x000000000b820000)
 PSOldGen        total 10240K, used 4096K [0x000000000a520000, 0x000000000af20000, 0x000000000af20000)
  object space 10240K, 40% used [0x000000000a520000,0x000000000a920018,0x000000000af20000)
 PSPermGen       total 21248K, used 2972K [0x0000000005120000, 0x00000000065e0000, 0x000000000a520000)
  object space 21248K, 13% used [0x0000000005120000,0x0000000005407388,0x00000000065e0000)

看到在Client模式下,最後分配的4M在新生代中,先分配的6M在老年代中;在Server模式下,最後分配的4M在老年代中,先分配的6M在新生代中。說明不同的垃圾收集器組合對於物件的分配是有影響的。講下兩者差別的原因:

1、Client模式下,新生代分配了6M,虛擬機器在GC前有6487K,比6M也就是6144K多,多主要是因為TLAB和EdenAllocationTest這個物件佔的空間,TLAB可以通過“-XX:+PrintTLAB”這個虛擬機器引數來檢視大小。OK,6M多了,然後來了一個4M的,Eden+一個Survivor總共就9M不夠分配了,這時候就會觸發一次Minor GC。但是觸發Minor GC也沒用,因為allocation1、allocation2、allocation3三個引用還存在,另一塊1M的Survivor也不夠放下這6M,那麼這次Minor GC的效果其實是通過分配擔保機制將這6M的內容轉入老年代中。然後再來一個4M的,由於此時Minor GC之後新生代只剩下了194K了,夠分配了,所以4M順利進入新生代。

2、Server模式下,前面都一樣,但是在GC的時候有一點區別。在GC前還會進行一次判斷,如果要分配的記憶體>=Eden區大小的一半,那麼會直接把要分配的記憶體放入老年代中。要分配4M,Eden區8M,剛好一半,而且老年代10M,夠分配,所以4M就直接進入老年代去了。為了驗證一下結論,我們把3個2M之後分配的4M改為3M看一下

public class EdenAllocationTest
{
    private static final int _1MB = 1024 * 1024;
    
    public static void main(String[] args)
    {
        byte[] allocation1 = new byte[2 * _1MB];
        byte[] allocation2 = new byte[2 * _1MB];
        byte[] allocation3 = new byte[2 * _1MB];
        byte[] allocation4 = new byte[3 * _1MB];
    }
}

執行結果為:

[GC [PSYoungGen: 6487K->352K(9216K)] 6487K->6496K(19456K), 0.0035661 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 352K->0K(9216K)] [PSOldGen: 6144K->6338K(10240K)] 6496K->6338K(19456K) [PSPermGen: 2941K->2941K(21248K)], 0.0035258 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 3236K [0x000000000af40000, 0x000000000b940000, 0x000000000b940000)
  eden space 8192K, 39% used [0x000000000af40000,0x000000000b269018,0x000000000b740000)
  from space 1024K, 0% used [0x000000000b740000,0x000000000b740000,0x000000000b840000)
  to   space 1024K, 0% used [0x000000000b840000,0x000000000b840000,0x000000000b940000)
 PSOldGen        total 10240K, used 6338K [0x000000000a540000, 0x000000000af40000, 0x000000000af40000)
  object space 10240K, 61% used [0x000000000a540000,0x000000000ab70858,0x000000000af40000)
 PSPermGen       total 21248K, used 2982K [0x0000000005140000, 0x0000000006600000, 0x000000000a540000)
  object space 21248K, 14% used [0x0000000005140000,0x0000000005429890,0x0000000006600000)

看到3M在新生代中,6M通過分配擔保機制進入老年代了。

二、大物件直接進入老年代

需要大量連續記憶體空間的Java物件稱為大物件,大物件的出現會導致提前觸發垃圾收集以獲取更大的連續的空間來進行大物件的分配。虛擬機器提供了-XX:PretenureSizeThreadshold引數來設定大物件的閾值,超過閾值的物件直接分配到老年代。

看下面的程式碼,虛擬機器引數為“-XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728”,最後那個引數表示大於這個設定值的物件直接在老年代中分配,這樣做的目的是為了避免在Eden區和兩個Survivor區之間發生大量的記憶體複製。

public class OldTest
{
    private static final int _1MB = 1024 * 1024;
    
    public static void main(String[] args)
    {
         byte[] allocation = new byte[4 * _1MB];
    }
}

Client模式下

Heap
 def new generation   total 9216K, used 507K [0x0000000005140000, 0x0000000005b40000, 0x0000000005b40000)
  eden space 8192K,   6% used [0x0000000005140000, 0x00000000051bef28, 0x0000000005940000)
  from space 1024K,   0% used [0x0000000005940000, 0x0000000005940000, 0x0000000005a40000)
  to   space 1024K,   0% used [0x0000000005a40000, 0x0000000005a40000, 0x0000000005b40000)
 tenured generation   total 10240K, used 4096K [0x0000000005b40000, 0x0000000006540000, 0x0000000006540000)
   the space 10240K,  40% used [0x0000000005b40000, 0x0000000005f40018, 0x0000000005f40200, 0x0000000006540000)
 compacting perm gen  total 21248K, used 2972K [0x0000000006540000, 0x0000000007a00000, 0x000000000b940000)
   the space 21248K,  13% used [0x0000000006540000, 0x00000000068272a0, 0x0000000006827400, 0x0000000007a00000)
No shared spaces configured.

Server模式下

Heap
 PSYoungGen      total 9216K, used 4603K [0x000000000afc0000, 0x000000000b9c0000, 0x000000000b9c0000)
  eden space 8192K, 56% used [0x000000000afc0000,0x000000000b43ef40,0x000000000b7c0000)
  from space 1024K, 0% used [0x000000000b8c0000,0x000000000b8c0000,0x000000000b9c0000)
  to   space 1024K, 0% used [0x000000000b7c0000,0x000000000b7c0000,0x000000000b8c0000)
 PSOldGen        total 10240K, used 0K [0x000000000a5c0000, 0x000000000afc0000, 0x000000000afc0000)
  object space 10240K, 0% used [0x000000000a5c0000,0x000000000a5c0000,0x000000000afc0000)
 PSPermGen       total 21248K, used 2972K [0x00000000051c0000, 0x0000000006680000, 0x000000000a5c0000)
  object space 21248K, 13% used [0x00000000051c0000,0x00000000054a72a0,0x0000000006680000)

看到Client模式下Eden空間幾乎沒有被使用,而老年代的10MB空間被使用了40%,也就是4MB的allocation物件直接就分配在老年代中,這是因為 PretenureSizeThreshold 被設定為3MB(就是3145728,這個引數不能像-Xmx 之類的引數一樣直接寫3MB),因此超過3MB的物件都會直接在老年代進行分配。Server模式下4M還在新生代中。產生這個差別的原因是“-XX:PretenureSizeThreshold”這個引數對Serial+Serial Old垃圾收集器組合有效而對Parallel+Serial Old垃圾收集器組合無效

三、長期存活的物件進入老年代

     每個物件有一個物件年齡計數器,與前面的物件的儲存佈局中的GC分代年齡對應。物件出生在Eden區、經過一次Minor GC後仍然存活,並能夠被Survivor容納,設定年齡為1,物件在Survivor區每次經過一次Minor GC,年齡就加1,當年齡達到一定程度(預設15),就晉升到老年代,虛擬機器提供了-XX:MaxTenuringThreshold來進行設定。

具體程式碼如下:

public class AllocationTest {
    private static final int _1MB = 1024 * 1024;
    
    /*
     *     -Xms20M -Xmx20M -Xmn10M 
        -XX:SurvivorRatio=8 
        -XX:+PrintGCDetails
        -XX:+UseSerialGC
        -XX:MaxTenuringThreshold=1
        -XX:+PrintTenuringDistribution
     * */
    public static void testTenuringThreshold() {
        byte[] allocation1, allocation2, allocation3;
        allocation1 = new byte[_1MB / 4];
        allocation2 = new byte[4 * _1MB];
        allocation3 = new byte[4 * _1MB];
        allocation3 = null;
        allocation3 = new byte[4 * _1MB];
    }
    
    public static void main(String[] args) {
        testPretenureSizeThreshold();
    }
}

執行結果:

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     790400 bytes,     790400 total
: 5174K->771K(9216K), 0.0050541 secs] 5174K->4867K(19456K), 0.0051088 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
: 4867K->0K(9216K), 0.0015279 secs] 8963K->4867K(19456K), 0.0016327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4260K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  52% used [0x00000000fec00000, 0x00000000ff0290e0, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4867K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  47% used [0x00000000ff600000, 0x00000000ffac0d30, 0x00000000ffac0e00, 0x0000000100000000)
 Metaspace       used 2562K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

說明:發生了兩次Minor GC,第一次是在給allocation3進行分配的時候會出現一次Minor GC,此時survivor區域不能容納allocation2,但是可以容納allocation1,所以allocation1將會進入survivor區域並且年齡為1,達到了閾值,將在下一次GC時晉升到老年代,而allocation2則會通過擔保機制進入老年代。第二次發生GC是在第二次給allocation3分配空間時,這時,allocation1的年齡加1,晉升到老年代,此次GC也可以清理出原來allocation3佔據的4MB空間,將allocation3分配在Eden區。所以,最後的結果是allocation1、allocation2在老年代,allocation3在Eden區。

四、動態物件年齡判斷

     物件的年齡到達了MaxTenuringThreshold可以進入老年代,同時,如果在survivor區中相同年齡所有物件大小的總和大於survivor區的一半,年齡大於等於該年齡的物件就可以直接進入老年代。無需等到MaxTenuringThreshold中要求的年齡。

具體程式碼如下:

public class AllocationTest {
    private static final int _1MB = 1024 * 1024;
    
    /*
     *     -Xms20M -Xmx20M -Xmn10M 
        -XX:SurvivorRatio=8 
        -XX:+PrintGCDetails
        -XX:+UseSerialGC
        -XX:MaxTenuringThreshold=15
        -XX:+PrintTenuringDistribution
     * */
    
    public static void testTenuringThreshold2() {
        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];
        allocation4 = null;
        allocation4 = new byte[4 * _1MB];
    }
    
    public static void main(String[] args) {
        testPretenureSizeThreshold2();
    }
}

執行結果:

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age   1:    1048576 bytes,    1048576 total
: 5758K->1024K(9216K), 0.0049451 secs] 5758K->5123K(19456K), 0.0049968 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
: 5120K->0K(9216K), 0.0016442 secs] 9219K->5123K(19456K), 0.0016746 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4178K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 5123K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  50% used [0x00000000ff600000, 0x00000000ffb00f80, 0x00000000ffb01000, 0x0000000100000000)
 Metaspace       used 2568K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

結果說明:發生了兩次Minor GC,第一次發生在給allocation4分配記憶體時,此時allocation1、allocation2將會進入survivor區,而allocation3通過擔保機制將會進入老年代。第二次發生在給allocation4分配記憶體時,此時,survivor區的allocation1、allocation2達到了survivor區容量的一半,將會進入老年代,此次GC可以清理出allocation4原來的4MB空間,並將allocation4分配在Eden區。最終,allocation1、allocation2、allocation3在老年代,allocation4在Eden區。

五、空間分配擔保

     在發生Minor GC時,虛擬機器會檢查老年代連續的空閒區域是否大於新生代所有物件的總和,若成立,則說明Minor GC是安全的,否則,虛擬機器需要檢視HandlePromotionFailure的值,看是否執行擔保失敗,若允許,則虛擬機器繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代物件的平均大小,若大於,將嘗試進行一次Minor GC;若小於或者HandlePromotionFailure設定不執行冒險,那麼此時將改成一次Full GC,以上是JDK Update 24之前的策略,之後的策略改變了,只要老年代的連續空間大於新生代物件總大小或者歷次晉升的平均大小就會進行Minor GC,否則將進行Full GC。

  冒險是指經過一次Minor GC後有大量物件存活,而新生代的survivor區很小,放不下這些大量存活的物件,所以需要老年代進行分配擔保,把survivor區無法容納的物件直接進入老年代。

具體的流程圖如下:

具體程式碼如下:

public class AllocationTest {
    private static final int _1MB = 1024 * 1024;
    
    /*
     *     -Xms20M -Xmx20M -Xmn10M 
        -XX:SurvivorRatio=8 
        -XX:+PrintGCDetails
        -XX:+UseSerialGC
        -XX:+HandlePromotionFailure
     * */
    
    public static void testHandlePromotion() {
        byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7,
        allocation8;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation1 = null;
        allocation4 = new byte[2 * _1MB];
        allocation5 = new byte[2 * _1MB];
        allocation6 = new byte[2 * _1MB];
        allocation4 = null;
        allocation5 = null;
        allocation6 = null;
        allocation7 = new byte[2 * _1MB];
    }
    
    public static void main(String[] args) {
        testHandlePromotion();
    }
}

執行結果:

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age   1:     528280 bytes,     528280 total
: 7294K->515K(9216K), 0.0040766 secs] 7294K->4611K(19456K), 0.0041309 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
: 6818K->0K(9216K), 0.0012444 secs] 10914K->4611K(19456K), 0.0012760 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 2130K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4611K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  45% used [0x00000000ff600000, 0x00000000ffa80d58, 0x00000000ffa80e00, 0x0000000100000000)
 Metaspace       used 2568K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 275K, capacity 386K, committed 512K, reserved 1048576K

說明:發生了兩次GC,第一次發生在給allocation4分配記憶體空間時,由於老年代的連續可用空間大於存活的物件總和,所以allocation2、allocation3將會進入老年代,allocation1的空間將被回收,allocation4分配在新生代;第二次發生在給allocation7分配記憶體空間時,此次GC將allocation4、allocation5、allocation6所佔的記憶體全部回收。最後,allocation2、allocation3在老年代,allocation7在新生代。

 

=============== End