深入理解java虛擬機(jī)-JVM高級特性和最佳實現(xiàn)(三)——深入垃圾收集器和內(nèi)存分配策略

每篇一葉

前言

上回說道自動內(nèi)存管理機(jī)制,那么沒用的對象該怎么處理呢?內(nèi)存又該怎么分配侦高?為了應(yīng)對這些場景械馆,這回我們引入垃圾收集器和內(nèi)存分配策略胖眷。

基本概念

  • 垃圾收集器(Garbage Collection,GC)
    這并不是一門java語言的伴生產(chǎn)物,實際上比java語言早半個多世紀(jì)問世霹崎。1960年MIT的Lisp是第一門真正使用內(nèi)存動態(tài)分配和垃圾收集技術(shù)的語言珊搀。

    我們在上回就知道了程序計數(shù)器,虛擬機(jī)棧尾菇,本地方法棧這三個區(qū)域隨著線程而生境析,隨著線程而滅,不需要考慮回收問題派诬,線程結(jié)束劳淆,內(nèi)存就跟著回收了。但是java堆和方法區(qū)就不一樣了默赂,每個實例需要的內(nèi)存可能不一樣沛鸵,一個方法的多個分支需要的內(nèi)存也有可能不一樣,我們只有在程序處于運行期間才能知道會創(chuàng)建哪些對象缆八,分配內(nèi)存和回收都是動態(tài)的曲掰。因此,垃圾收集器關(guān)注的就是這部分內(nèi)存耀里。

  • 如何確定對象是否該回收

    1. 引用計數(shù)算法
      給對象添加一個引用計數(shù)器蜈缤,引用一次則加一,引用失效則減一冯挎,當(dāng)引用為0的對象則表示不能再被使用底哥。
      這是個不錯的算法咙鞍,如微軟的COM技術(shù),使用ActionScript3的FlashPlayer趾徽、python語言和游戲領(lǐng)域的Squirrel都采用這個算法進(jìn)行內(nèi)存管理续滋,然而主流的java虛擬機(jī)并沒有采用引用計數(shù)算法管理內(nèi)存,主要是很難處理對象之間相互循環(huán)引用的問題孵奶。

    2. 可達(dá)性算法
      以一個GC Roots的對象為起點疲酌,從這些節(jié)點向下探索,探索走過的路徑為引用鏈了袁,當(dāng)一個對象到GC Roots沒有任何引用鏈時朗恳,證明這個對象是不可用的。


      可達(dá)性算法

    不可達(dá)對象也并非一定會被回收载绿,要真正宣布對象死亡粥诫,至少需要經(jīng)歷兩次標(biāo)記過程:如果對象進(jìn)行可達(dá)性分析發(fā)現(xiàn)沒有與GC Roots相連接,那么崭庸,它會被第一次標(biāo)記并且進(jìn)行一次篩選怀浆,篩選條件是此對象是否有必要執(zhí)行finalize()方法。對象如果沒有覆蓋該方法怕享,或者改方法已經(jīng)被虛擬機(jī)調(diào)用過执赡,則虛擬機(jī)認(rèn)為沒有必要執(zhí)行。對象將會進(jìn)入一個叫F-Queue的隊列中函筋。GC將會對隊列中的對象第二次小規(guī)模標(biāo)記沙合,如果對象不通過finalize()方法自救的話,對象將會被回收驻呐。當(dāng)然這種方式拯救對象是不推薦的灌诅,運行代價高昂,不確定性大含末,使用try-finally方式可能做得更好猜拾。

JDK中對Object.finalize()方法的解釋是

當(dāng)垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調(diào)用此方法佣盒。子類重寫 finalize 方法挎袜,以配置系統(tǒng)資源或執(zhí)行其他清除。
對于任何給定對象肥惭,Java 虛擬機(jī)最多只調(diào)用一次 finalize 方法盯仪。

我們可以理解為當(dāng)顯示的調(diào)用System.gc()時,java虛擬機(jī)會調(diào)用一次finalize()方法蜜葱,由于這個方法是protected類型的所以優(yōu)先級比較低全景,我們需要通過sleep方法來等待。
當(dāng)?shù)诙握{(diào)用System.gc()時牵囤,虛擬機(jī)是不會再次執(zhí)行finalize方法爸黄。
以下是關(guān)于finalize()方法自救的案例滞伟。

public class FinalizaEscapeGC {
    public static FinalizaEscapeGC SAVE_HOKE = null;
    
    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        System.out.println("finalize method execute ! ");
        FinalizaEscapeGC.SAVE_HOKE = this;
    }
    
    public static void main(String[] args) throws InterruptedException {
        SAVE_HOKE = new FinalizaEscapeGC();
        
        SAVE_HOKE = null;
        System.gc();
        Thread.sleep(500);
        if(SAVE_HOKE != null){
            System.out.println("yes,i am still alive !");
        }else{
            System.out.println("no,i am dead !");
        }
        
        SAVE_HOKE = null;
        System.gc();
        Thread.sleep(500);
        if(SAVE_HOKE != null){
            System.out.println("yes,i am still alive !");
        }else{
            System.out.println("no,i am dead !");
        }
    }
}
  1. 回收方法區(qū)
    其實 java虛擬機(jī)規(guī)范中說過可以不要求虛擬機(jī)在方法區(qū)實現(xiàn)垃圾收集,因此很多人認(rèn)為方法區(qū)(永久代)是沒有垃圾收集的炕贵。方法區(qū)進(jìn)行垃圾收集效率十分低梆奈,在堆中新生代一次垃圾收集一般可以回收70%-95%的空間,而永久代的垃圾收集效率遠(yuǎn)低于此称开。
    判斷一個類是否是無用類亩钟,我們需要滿足一下三點:
    a. 該類所有實例都已經(jīng)被回收
    b. 該類的ClassLoader已經(jīng)被回收
    c. 該類對象的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法鳖轰。
  • 垃圾收集算法
  1. 標(biāo)記-清除算法
    最基礎(chǔ)的收集算法清酥。先標(biāo)記需要回收的對象,然后統(tǒng)一回收所有標(biāo)記的對象脆霎。
    缺點:效率不高总处,標(biāo)記清除后會產(chǎn)生大量的內(nèi)存碎片,碎片太多可能會導(dǎo)致需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不觸發(fā)下一次標(biāo)記-清除算法睛蛛。


    標(biāo)記-清除算法
  1. 復(fù)制算法
    針對上一種算法,解決效率問題胧谈,就有了復(fù)制算法忆肾。將內(nèi)存按容量分成兩塊,每次只使用一塊菱肖。當(dāng)一塊內(nèi)存用完了客冈,就將存過的對象復(fù)制到另一塊上面,然后將使用過的內(nèi)存塊一次清理掉稳强。
    缺點:內(nèi)存縮小一半场仲,代價太高。


    復(fù)制算法
  1. 標(biāo)記-清理算法
    復(fù)制算法在對象存活率較高時就要進(jìn)行較多的復(fù)制操作退疫,效率會變低渠缕,更關(guān)鍵的是浪費50%空間。因此老年代一般不能直接選用這種算法褒繁。
    標(biāo)記-整理算法和標(biāo)記-清除算法一樣亦鳞,但是后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓存活對象都向一端移動棒坏,然后清理掉邊端以外的內(nèi)存燕差。


    標(biāo)記-整理算法
  1. 分代收集算法
    當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用這個算法。將內(nèi)存劃分為幾塊坝冕,一般吧java堆分為新生代和老年代徒探,根據(jù)每個年代的特點采用最適合的收集算法。
    在新生代中喂窟,每次垃圾收集時都有大批對象死亡测暗,只有少量存活吵血,那就選用復(fù)制算法。老年代必須使用標(biāo)記-清理或者標(biāo)記-整理算法來回收偷溺。

實例

  • HotSpot的算法實現(xiàn)
    1.Stop The World 枚舉根節(jié)點
    可達(dá)性算法以GC Roots 節(jié)點尋找引用鏈需要耗費很多時間蹋辅,可達(dá)性分析對于執(zhí)行時間的敏感體現(xiàn)在了GC停頓上。分析工作必須在一個能確保一致性的快照中進(jìn)行挫掏,即時間凍結(jié)在某個點侦另。這就是Stop The World ,即使在幾乎不會停頓的CMS收集器中尉共,枚舉根節(jié)點時也是必須停頓的褒傅。
  1. 安全點。
    OopMap幫助HotSpot快速完成GC Roots枚舉袄友。
    長時間執(zhí)行產(chǎn)生safepoint,體現(xiàn)在指令序列復(fù)用殿托,如方法調(diào)用,循環(huán)跳轉(zhuǎn)剧蚣,異常跳轉(zhuǎn)等支竹。
  2. 安全區(qū)域

-垃圾收集器


HotSpot虛擬機(jī)的垃圾收集器
  1. Serial 收集器
    最基本、發(fā)展最悠久的收集器鸠按,曾經(jīng)虛擬機(jī)新生代收集的唯一選擇礼搁。它是一個單線程收集器。
    有Stop The World的不良體驗目尖÷猓“你媽媽在給你打掃房間的時候,肯定會讓你老老實實地在椅子上或者房間外待著瑟曲,如果她一邊打掃饮戳,你一邊亂扔紙屑,這房間還能打掃完洞拨?”這個理由還是很合理的扯罐。


    Serial/Serial Old收集器運行示意圖
  2. ParNew收集器
    其實就是Serial收集器的多線程版本。它能做到一邊打掃扣甲,一邊讓你扔紙屑篮赢。


    ParNew/ParOld收集器運行示意圖
  3. Parallel Scavenge收集器
    這是一個使用復(fù)制算法的新生代收集器,也是并行的多線程收集器琉挖,它關(guān)注的是吞吐量启泣。吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)。


    Parallel Scavenge/Parallel Old收集器運行示意圖
  4. Serial Old 收集器
    Serial收集器的老年代版本示辈,也是個單線程收集器寥茫,使用的是標(biāo)記-整理算法。

  5. Parallel Old
    老年代的Parallel Scavenge
    6.CMS收集器
    Concurrent Mark Sweep 收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器矾麻。
    基于標(biāo)記-清除算法實現(xiàn)的纱耻,運行過程較前面幾種收集器來說復(fù)雜了點芭梯,分為四個步驟即初始標(biāo)記、并發(fā)標(biāo)記弄喘、重新標(biāo)記玖喘、并發(fā)清除。其中初始標(biāo)記蘑志、重新標(biāo)記兩個步驟仍然需要Stop The World .初始標(biāo)記標(biāo)記下GC Roots能直接關(guān)聯(lián)的對象累奈,速度很快。并發(fā)標(biāo)記是進(jìn)行GC Roots Tracing過程急但,重新標(biāo)記修正并發(fā)標(biāo)記期間因用戶程序運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄澎媒,耗時遠(yuǎn)小于并發(fā)標(biāo)記時間。
    CMS收集器的缺點:
    a.對CPU資源敏感波桩,會降低總吞吐量
    b.無法處理浮動垃圾
    c.基于標(biāo)記-清除算法的弊端

Concurrent Mark Sweep 收集器運行示意圖

7.G1收集器
這是收集器技術(shù)發(fā)展的最前沿成果之一戒努,是為取代CMS收集器而生的。特點有:并行和并發(fā)镐躲、分代收集储玫、空間整合、可預(yù)測的停頓匀油。G1收集器大致分四個步驟:初始標(biāo)記缘缚、并發(fā)標(biāo)記、最終標(biāo)記敌蚜、篩選回收。


Garbage-First收集器運行示意圖
  • 內(nèi)存分配與回收策略
  1. 對象優(yōu)先在新生代Eden區(qū)分配窝爪。
  2. 大對象直接進(jìn)入老年代弛车。
    大對象指:很長的字符串,數(shù)組等蒲每。在開發(fā)的時候應(yīng)該盡量避免那種短命的大對象纷跛。
  3. 長期存活的對象將進(jìn)入老年代。
  • 實戰(zhàn)
  1. 開啟eclipseGC日志


    開啟eclipse gc日志

    其中:-XX:+PrintGCDetails為收集日志參數(shù)邀杏,-Xms20M -Xmx20M -Xmn10m 表示限制java堆20M內(nèi)存贫奠,其中分配給新生代10M,老年代剩下的10M望蜡。-XX:SurvivorRatio=8決定了新生代中Eden區(qū)與一個Survivor區(qū)的空間比例是8:1

  2. 理解java堆的結(jié)構(gòu)
    java堆分新生代和老年代唤崭,新生代由一個Eden和兩個Survivor組成,默認(rèn)比例為8比1脖律,即我們分配給新生代10M內(nèi)存谢肾,Eden為8M,Survivor各占1M小泉。兩個Survivor用from芦疏,to 區(qū)分冕杠。


    java堆內(nèi)存分布圖
  3. 理解gc日志


    gc日志片段

    33.125,100.667表示的是gc發(fā)生的時間酸茴,這個是虛擬機(jī)啟動以來的秒數(shù)
    [GC 和 [Full GC 表示垃圾收集的停頓類型俘侠,并不是用來區(qū)分老年代和新生代。如果是Full GC 表示發(fā)生過stop the world秀睛。
    [DefNew擅威,[Tenured ,[Perm 表示GC發(fā)生的區(qū)域飘诗。
    方括號里面的3324K->152K(3712K) 表示GC前該內(nèi)存已使用容量->GC后該內(nèi)存已使用容量(該內(nèi)存總?cè)萘浚┯氤嚼ㄌ柾饷娴?324K->152K(11904K)表示GC前java堆已使用容量->GC后java堆已使用容量(該內(nèi)存總?cè)萘浚竺娴?.0031680 secs 表示該內(nèi)存區(qū)域GC所占用的時間昆稿,單位是秒纺座。更詳細(xì)一點的是[Times:user=0.01 sys=0.00,real=0.02 secs] 表示用戶、操作內(nèi)核溉潭、操作從開始到結(jié)束的墻鐘時間净响。

  4. 案例
    a. 對象優(yōu)先存入Eden區(qū)

/*
 * 
 * VM 參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8
 *
 */
public class WatchGcDemo1 {
    private static final int _1MB = 1024*1024;
    public static void testAllocation(){
        byte[] a1,a2,a3,a4;
        a1 = new byte[2*_1MB];
        a2 = new byte[2*_1MB];
        a3 = new byte[2*_1MB];
        a4 = new byte[4*_1MB];
    }
    public static void main(String[] args) {
        testAllocation();
    }
}

控制臺打印

[GC [DefNew: 6487K->150K(9216K), 0.0047964 secs] 6487K->6294K(19456K), 0.0048239 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4574K [0x330f0000, 0x33af0000, 0x33af0000)
  eden space 8192K,  54% used [0x330f0000, 0x33541fa8, 0x338f0000)
  from space 1024K,  14% used [0x339f0000, 0x33a15b68, 0x33af0000)
  to   space 1024K,   0% used [0x338f0000, 0x338f0000, 0x339f0000)
 tenured generation   total 10240K, used 6144K [0x33af0000, 0x344f0000, 0x344f0000)
   the space 10240K,  60% used [0x33af0000, 0x340f0030, 0x340f0200, 0x344f0000)
 compacting perm gen  total 12288K, used 375K [0x344f0000, 0x350f0000, 0x384f0000)
   the space 12288K,   3% used [0x344f0000, 0x3454dd90, 0x3454de00, 0x350f0000)
    ro space 10240K,  55% used [0x384f0000, 0x38a70f00, 0x38a71000, 0x38ef0000)
    rw space 12288K,  55% used [0x38ef0000, 0x395942f0, 0x39594400, 0x39af0000)

程序中給a4賦值的時候發(fā)生了一次新生代GC(Minor GC), [DefNew: 6487K->150K(9216K), 0.0047964 secs] 6487K->6294K(19456K), 0.0048239 secs] [Times: user=0.00 sys=0.00, real=0.00 secs],新生代從6487K變成150K喳瓣,而堆內(nèi)存6487K到6294K馋贤,幾乎沒有變化(虛擬機(jī)沒有找到可回收對象)。
GC發(fā)生原因是由于a4需要分配4M內(nèi)存時畏陕,發(fā)現(xiàn)Eden占6M配乓,剩余的內(nèi)存不足以分配,因此發(fā)生了Minor GC惠毁。GC期間虛擬機(jī)發(fā)現(xiàn)三個2M內(nèi)存的對象無法放入Survivor區(qū)(Survivor區(qū)只有1M內(nèi)存空間)犹芹,只好通過分配擔(dān)保機(jī)制提前轉(zhuǎn)移到老年代中去了。
這次GC結(jié)束后鞠绰,a4成功的放入到了Eden中腰埂,因此,程序結(jié)束后內(nèi)存占用情況是:Eden 4M蜈膨,Survivor空閑屿笼,老年代占用6M。

b. 大對象直接進(jìn)入老年代
首先我們設(shè)置運行參數(shù)-XX:PretenureSizeThreshold=3145728翁巍,這樣超過3M的對象會直接進(jìn)入老年代驴一。PretenureSizeThreshold只對ParNew和Serial兩種收集器生效。
示例代碼

    private static final int _1MB = 1024*1024;
    /**
     * VM 參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
     */
    public static void testPretenureThreshold(){
        byte[] a = new byte[3*_1MB];
    }

運行結(jié)果

Heap
 def new generation   total 9216K, used 507K [0x330f0000, 0x33af0000, 0x33af0000)
  eden space 8192K,   6% used [0x330f0000, 0x3316ef08, 0x338f0000)
  from space 1024K,   0% used [0x338f0000, 0x338f0000, 0x339f0000)
  to   space 1024K,   0% used [0x339f0000, 0x339f0000, 0x33af0000)
 tenured generation   total 10240K, used 3072K [0x33af0000, 0x344f0000, 0x344f0000)
   the space 10240K,  30% used [0x33af0000, 0x33df0010, 0x33df0200, 0x344f0000)
 compacting perm gen  total 12288K, used 375K [0x344f0000, 0x350f0000, 0x384f0000)
   the space 12288K,   3% used [0x344f0000, 0x3454de70, 0x3454e000, 0x350f0000)
    ro space 10240K,  55% used [0x384f0000, 0x38a70f00, 0x38a71000, 0x38ef0000)
    rw space 12288K,  55% used [0x38ef0000, 0x395942f0, 0x39594400, 0x39af0000)

我們不難發(fā)現(xiàn)def new generation 新生代幾乎沒有變化曙咽,tenured generation老年代使用30%蛔趴,就是我們的a對象。

c. 長期存活的對象將進(jìn)入老年代
哪些對象該存放在新生代,哪些對象又該存放在老年代孝情,虛擬機(jī)是通過一個年齡(Age)計數(shù)器來判斷鱼蝉。如果對象在Eden出生并經(jīng)歷了一次Minor GC 后,并且可以背Survivor容納的話箫荡,將會被移入到Survivor區(qū)魁亦,對象年齡設(shè)為1。以后對象每熬過一次Minor GC年齡會增加1歲羔挡,當(dāng)增加到15歲(默認(rèn)的)洁奈,會被晉升到老年代。對象晉級年齡的閾值通過-XX:MaxTenuringThreshold=1設(shè)置绞灼。
示例代碼

/**
     * 長期存活的對象將進(jìn)入老年代
     * VM 參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
     */
    public static void testTenuringThreshold(){
        byte[] a1,a2,a3;
        a1 = new byte[_1MB/4];
        a2 = new byte[4*_1MB];
        a3 = new byte[4*_1MB];
        a3 = null;
        a3 = new byte[4*_1MB];
    }

運行結(jié)果

[GC [DefNew: 4695K->406K(9216K), 0.0038531 secs] 4695K->4502K(19456K), 0.0038787 secs] [Times: user=0.00 sys=0.02, real=0.00 secs] 
[GC [DefNew: 4666K->0K(9216K), 0.0007054 secs] 8762K->4502K(19456K), 0.0007278 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4260K [0x330f0000, 0x33af0000, 0x33af0000)
  eden space 8192K,  52% used [0x330f0000, 0x33518fe0, 0x338f0000)
  from space 1024K,   0% used [0x338f0000, 0x338f0088, 0x339f0000)
  to   space 1024K,   0% used [0x339f0000, 0x339f0000, 0x33af0000)
 tenured generation   total 10240K, used 4502K [0x33af0000, 0x344f0000, 0x344f0000)
   the space 10240K,  43% used [0x33af0000, 0x33f55ab8, 0x33f55c00, 0x344f0000)
 compacting perm gen  total 12288K, used 375K [0x344f0000, 0x350f0000, 0x384f0000)
   the space 12288K,   3% used [0x344f0000, 0x3454df60, 0x3454e000, 0x350f0000)
    ro space 10240K,  55% used [0x384f0000, 0x38a70f00, 0x38a71000, 0x38ef0000)
    rw space 12288K,  55% used [0x38ef0000, 0x395942f0, 0x39594400, 0x39af0000)

不難看出利术,發(fā)生了兩次Minor GC ,當(dāng)?shù)谝淮伟l(fā)生GC時對象a1,256K被移入到了Survivor區(qū)(1M空間),這個時候年齡為1低矮,而我們設(shè)定的閾值為1印叁,當(dāng)?shù)诙蜧C的時候,a1達(dá)到2歲军掂,就被移入到了老年代轮蜕。from,to中為0,如果我們設(shè)置閾值為3時蝗锥,結(jié)果如下

[GC [DefNew: 4695K->406K(9216K), 0.0032536 secs] 4695K->4502K(19456K), 0.0032839 secs] [Times: user=0.00 sys=0.02, real=0.00 secs] 
[GC [DefNew: 4666K->406K(9216K), 0.0007343 secs] 8762K->4502K(19456K), 0.0007595 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4666K [0x330f0000, 0x33af0000, 0x33af0000)
  eden space 8192K,  52% used [0x330f0000, 0x33518fe0, 0x338f0000)
  from space 1024K,  39% used [0x338f0000, 0x33955b30, 0x339f0000)
  to   space 1024K,   0% used [0x339f0000, 0x339f0000, 0x33af0000)
 tenured generation   total 10240K, used 4096K [0x33af0000, 0x344f0000, 0x344f0000)
   the space 10240K,  40% used [0x33af0000, 0x33ef0010, 0x33ef0200, 0x344f0000)
 compacting perm gen  total 12288K, used 375K [0x344f0000, 0x350f0000, 0x384f0000)
   the space 12288K,   3% used [0x344f0000, 0x3454df60, 0x3454e000, 0x350f0000)
    ro space 10240K,  55% used [0x384f0000, 0x38a70f00, 0x38a71000, 0x38ef0000)
    rw space 12288K,  55% used [0x38ef0000, 0x395942f0, 0x39594400, 0x39af0000)

from space 1024K, 39% 跃洛,證明對象a1還在Survivor區(qū)。

d. 動態(tài)對象年齡判定
為了更好的適應(yīng)不同程序的內(nèi)存狀況终议,虛擬機(jī)并不是永遠(yuǎn)要求對象的年齡必須到到閾值才能晉升汇竭,如果Survivor空間中相同年齡的對象大小總和大于Survivor空間的一半,年紀(jì)大于或等于該年齡的對象直接進(jìn)入老年代穴张,無需打到閾值中要求的年齡韩玩。示例代碼如下

/**
     * 動態(tài)對象年齡判定
     * VM 參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
     */
    public static void testTenuringThreshold2(){
        byte[] a1,a2,a3,a4;
        a1 = new byte[_1MB/4];
        a2 = new byte[_1MB/4];
        a3 = new byte[4*_1MB];
        a4 = new byte[4*_1MB];
        a4 = null;
        a4 = new byte[4*_1MB];
    }

運行結(jié)果

[GC [DefNew: 4951K->662K(9216K), 0.0034462 secs] 4951K->4758K(19456K), 0.0034710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [DefNew: 4922K->0K(9216K), 0.0010763 secs] 9018K->4758K(19456K), 0.0011015 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4260K [0x330f0000, 0x33af0000, 0x33af0000)
  eden space 8192K,  52% used [0x330f0000, 0x33518fe0, 0x338f0000)
  from space 1024K,   0% used [0x338f0000, 0x338f0088, 0x339f0000)
  to   space 1024K,   0% used [0x339f0000, 0x339f0000, 0x33af0000)
 tenured generation   total 10240K, used 4758K [0x33af0000, 0x344f0000, 0x344f0000)
   the space 10240K,  46% used [0x33af0000, 0x33f95ac8, 0x33f95c00, 0x344f0000)
 compacting perm gen  total 12288K, used 376K [0x344f0000, 0x350f0000, 0x384f0000)
   the space 12288K,   3% used [0x344f0000, 0x3454e060, 0x3454e200, 0x350f0000)
    ro space 10240K,  55% used [0x384f0000, 0x38a70f00, 0x38a71000, 0x38ef0000)
    rw space 12288K,  55% used [0x38ef0000, 0x395942f0, 0x39594400, 0x39af0000)

e. 空間分配擔(dān)保
在發(fā)送Minor GC前,虛擬機(jī)會先檢查老年代最大 可用的連續(xù)空間是否大于新生代所有對象的總空間陆馁。如果成立,那么Minor GC可以確保是安全的合愈,否則叮贩,虛擬機(jī)會查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗,如果允許那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小佛析,如果大于益老,將嘗試一次Minor GC,盡管是有風(fēng)險的,如果小于或者HandlePromotionFailure設(shè)置不允許冒險寸莫,那么這時改為進(jìn)行一次Full GC捺萌。
示例代碼

/**
     * 空間分配擔(dān)保
     * VM 參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
     * 
     */
    public static void testHandlePromotion(){
        byte[] a1,a2,a3,a4,a5,a6,a7,a8;
        a1 = new byte[2*_1MB];
        a2 = new byte[2*_1MB];
        a3 = new byte[2*_1MB];
        a1 = null;
        a4 = new byte[2*_1MB];
        a5 = new byte[2*_1MB];
        a6 = new byte[2*_1MB];
        a4 = null;
        a5 = null;
        a6 = null;
        a7 = new byte[2*_1MB];
    }

運行結(jié)果

[GC [DefNew: 6487K->150K(9216K), 0.0029545 secs] 6487K->4246K(19456K), 0.0030091 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [DefNew: 6544K->150K(9216K), 0.0005309 secs] 10640K->4246K(19456K), 0.0005594 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 2362K [0x330f0000, 0x33af0000, 0x33af0000)
  eden space 8192K,  27% used [0x330f0000, 0x33318fe0, 0x338f0000)
  from space 1024K,  14% used [0x338f0000, 0x33915b20, 0x339f0000)
  to   space 1024K,   0% used [0x339f0000, 0x339f0000, 0x33af0000)
 tenured generation   total 10240K, used 4096K [0x33af0000, 0x344f0000, 0x344f0000)
   the space 10240K,  40% used [0x33af0000, 0x33ef0020, 0x33ef0200, 0x344f0000)
 compacting perm gen  total 12288K, used 376K [0x344f0000, 0x350f0000, 0x384f0000)
   the space 12288K,   3% used [0x344f0000, 0x3454e1d8, 0x3454e200, 0x350f0000)
    ro space 10240K,  55% used [0x384f0000, 0x38a70f00, 0x38a71000, 0x38ef0000)
    rw space 12288K,  55% used [0x38ef0000, 0x395942f0, 0x39594400, 0x39af0000)
Warning: The flag -HandlePromotionFailure has been EOL'd as of 6.0_24 and will be ignored

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市膘茎,隨后出現(xiàn)的幾起案子桃纯,更是在濱河造成了極大的恐慌酷誓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件态坦,死亡現(xiàn)場離奇詭異盐数,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)伞梯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門玫氢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谜诫,你說我怎么就攤上這事漾峡。” “怎么了喻旷?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵生逸,是天一觀的道長。 經(jīng)常有香客問我掰邢,道長牺陶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任辣之,我火速辦了婚禮掰伸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怀估。我一直安慰自己狮鸭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布多搀。 她就那樣靜靜地躺著歧蕉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪康铭。 梳的紋絲不亂的頭發(fā)上惯退,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音从藤,去河邊找鬼催跪。 笑死,一個胖子當(dāng)著我的面吹牛夷野,可吹牛的內(nèi)容都是我干的懊蒸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼悯搔,長吁一口氣:“原來是場噩夢啊……” “哼骑丸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤通危,失蹤者是張志新(化名)和其女友劉穎铸豁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黄鳍,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡推姻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了框沟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藏古。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖忍燥,靈堂內(nèi)的尸體忽然破棺而出拧晕,到底是詐尸還是另有隱情,我是刑警寧澤梅垄,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布厂捞,位于F島的核電站,受9級特大地震影響队丝,放射性物質(zhì)發(fā)生泄漏靡馁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一机久、第九天 我趴在偏房一處隱蔽的房頂上張望臭墨。 院中可真熱鬧,春花似錦膘盖、人聲如沸胧弛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽结缚。三九已至,卻和暖如春软棺,著一層夾襖步出監(jiān)牢的瞬間红竭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工喘落, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留德崭,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓揖盘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锌奴。 傳聞我的和親對象是個殘疾皇子兽狭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容