遇到一個(gè)對(duì)象分配的問題

最近看了畢玄老師分享的的PPT叶沛,對(duì)其中一個(gè)案例進(jìn)行了測(cè)試,參考代碼如下:

/**
 * -Xmx20M -Xms20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails
 * -XX:PretenureSizeThreshold=4M -Xloggc:D:\gc.log
 * @author heyong
 */
public class SerialGCDemo {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
        ByteArray bytes = new ByteArray(2 * _1MB);
        ByteArray bytes2 = new ByteArray(2 * _1MB);
        ByteArray bytes3 = new ByteArray(2 * _1MB);
        System.out.println("step 1");
        ByteArray bytes4 = new ByteArray(2 * _1MB);
        Thread.sleep(3000);
        System.out.println("step 2");
        ByteArray bytes5 = new ByteArray(2 * _1MB);
        ByteArray bytes6 = new ByteArray(2 * _1MB);
        System.out.println("step 3");
        ByteArray bytes7 = new ByteArray(3 * _1MB);
        // Thread.sleep(1000 * 60 * 60);
        System.out.println("======");
    }

    private static class ByteArray {
        private byte[] bytes;
        public ByteArray(int size) {
            bytes = new byte[size];
        }
    }
}

運(yùn)行的代碼和參數(shù)如上忘朝,在復(fù)雜代碼到機(jī)器里面運(yùn)行之前灰署,請(qǐng)大家思考下面2個(gè)問題:

  1. bytes7對(duì)象能否分配成功?
  2. 如果bytes7能夠分配成功局嘁,應(yīng)該分配在哪里呢溉箕?

如果大家對(duì)于對(duì)象的分配流程感興趣,可以參考我的博文:

對(duì)象的創(chuàng)建以及YoungGC的觸發(fā):http://www.reibang.com/p/941fe93d21c2
JVM之逃逸分析以及TLAB:http://www.reibang.com/p/3835450d49d0

好了言歸正傳悦昵,回答上面的問題约巷!

bytes7對(duì)象能否分配成功

上面的對(duì)象能夠分配成功,在 eclipse 執(zhí)行上面的程序旱捧,打印出來(lái)的GC日志如下:

0.341: [GC (Allocation Failure) 0.341: [DefNew: 7127K->526K(9216K), 0.0199660 secs] 7127K->6670K(19456K), 0.0202876 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
3.367: [GC (Allocation Failure) 3.368: [DefNew: 6830K->6830K(9216K), 0.0000683 secs]3.368: [Tenured: 6144K->8192K(10240K), 0.0581733 secs] 12974K->12812K(19456K), [Metaspace: 2655K->2655K(1056768K)], 0.0590199 secs] [Times: user=0.01 sys=0.00, real=0.06 secs] 

上面的日志中有兩條GC日志独郎,從第一條日志可以分析出,創(chuàng)建bytes4的時(shí)候枚赡,年輕代不足氓癌,觸發(fā)了一次YoungGC,年輕代存活對(duì)象大小 = 6M贫橙,To Survivor 區(qū)大小 = 1M贪婉,To區(qū)空間不夠,通過空間擔(dān)保機(jī)制晉升到老年代卢肃,老年代的晉升規(guī)則如下:

bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
  //老年代最大連續(xù)空間
  size_t available = max_contiguous_available();
  //平均晉升對(duì)象的大小
  size_t av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average();
  //最大連續(xù)空間大小 > 平均大小或最大連續(xù)空間 > 晉升對(duì)象的大小
  bool   res = (available >= av_promo) || (available >= max_promotion_in_bytes);
  if (PrintGC && Verbose) {
    gclog_or_tty->print_cr(
      "Tenured: promo attempt is%s safe: available("SIZE_FORMAT") %s av_promo("SIZE_FORMAT"),"
      "max_promo("SIZE_FORMAT")",
      res? "":" not", available, res? ">=":"<",
      av_promo, max_promotion_in_bytes);
  }
  return res;
}

從上面的代碼可以看出疲迂,如果最大連續(xù)空間大小 > 平均大小或最大連續(xù)空間 > 晉升對(duì)象的大小才顿,就運(yùn)行年輕代對(duì)象晉升到老年代,該程序中老年代空間 = 10m尤蒿,因此可以將年輕代對(duì)象晉升到老年代郑气。
下面繼續(xù)看第二條日志,發(fā)現(xiàn)年輕代在分配bytes7對(duì)象的時(shí)候腰池,年輕代不足觸發(fā)YoungGC,但是老年代空間不足尾组,無(wú)法滿足晉升規(guī)則,因此觸發(fā)了一次FullGC示弓,部分年輕代對(duì)象被晉升到老年代讳侨。從源碼角度分析一下為什么會(huì)觸發(fā)FullGC,源碼如下:

//表示有jni在操作內(nèi)存奏属,此時(shí)不能進(jìn)行GC避免改變對(duì)象在內(nèi)存的位置
if (GC_locker::is_active_and_needs_gc()) {
    if (!gch->is_maximal_no_gc()) {
      result = expand_heap_and_allocate(size, is_tlab);//擴(kuò)堆
    }
    return result;   // could be null if we are out of space
    //consult_young=true的時(shí)候跨跨,表示調(diào)用該方法時(shí),判斷此時(shí)晉升是否的安全的囱皿。
    //若=false歹叮,表示只取上次young gc時(shí)設(shè)置的參數(shù),此次不再進(jìn)行額外的判斷铆帽。
  } else if (!gch->incremental_collection_will_fail(false /* don't consult_young */)) {
    // 對(duì)年輕代進(jìn)行GC
    gch->do_collection(false            /* full */,
                       false            /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  } else {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print(" :: Trying full because partial may fail :: ");
    }
    //對(duì)老年代進(jìn)行GC
    gch->do_collection(true             /* full */,
                       false            /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  }
  //嘗試重新分配
  result = gch->attempt_allocation(size, is_tlab, false /*first_only*/);
  if (result != NULL) {
    assert(gch->is_in_reserved(result), "result not in heap");
    return result;
  }

  // 如果上面分配失敗咆耿,嘗試先闊堆,然后分配
  result = expand_heap_and_allocate(size, is_tlab);
  if (result != NULL) {
    return result;
  }
  //如果對(duì)象還是沒有分配成功爹橱,進(jìn)行一次FullGC
  {
    IntFlagSetting flag_change(MarkSweepAlwaysCompactCount, 1); // Make sure the heap is fully compacted

    gch->do_collection(true             /* full */,
                       true             /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  }
  //在FullGC以后嘗試分配
  result = gch->attempt_allocation(size, is_tlab, false /* first_only */);

從上面的代碼中可以看出如果進(jìn)行YoungGC分配失敗以后萨螺,會(huì)進(jìn)行一次FullGC,進(jìn)行對(duì)象的回收愧驱,從日志可以看出慰技,在進(jìn)行FullGC時(shí),部分年輕代對(duì)象晉升到了老年代组砚。

如果bytes7能夠分配成功吻商,應(yīng)該分配在哪里呢?

重新第二條日志糟红,老年代區(qū)內(nèi)存使用量增加了2M艾帐,有人可能會(huì)問老年代區(qū)增加的 2M 也可能是 bytes7 在老年代分配了啊盆偿?這里提供 3 種方法證明 bytes7 對(duì)象在年輕代分配

First

在上面的程序中將 :

 byte[] bytes7 = new byte[2 * _1MB];

修改為:

 byte[] bytes7 = new byte[3 * _1MB];

執(zhí)行上面的程序柒爸,查看日志:

Heap
 def new generation   total 9216K, used 8016K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  97% used [0x00000000fec00000, 0x00000000ff3d43f8, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  80% used [0x00000000ff600000, 0x00000000ffe00158, 0x00000000ffe00200, 0x0000000100000000)
 Metaspace       used 2661K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K

發(fā)現(xiàn)年輕代使用量 = 8016K = 7.8 M,老年代使用量 = 8192K = 8M事扭,bytes7分配了3M的內(nèi)存捎稚,因此可以證明bytes7 是在年輕代分配的。

Second

光從日志的角度還是不能證明,下面我們從源碼角度去證實(shí)一下(最好是先看:《對(duì)象的創(chuàng)建以及YoungGC的觸發(fā)》這篇文章):
在年輕代進(jìn)行對(duì)象分配的時(shí)候今野,會(huì)調(diào)用 GenCollectorPolicy::should_try_older_generation_allocation 方法來(lái)判斷對(duì)象是否在老年代分配葡公,具體實(shí)現(xiàn)如下:

bool GenCollectorPolicy::should_try_older_generation_allocation(
        size_t word_size) const {
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  size_t gen0_capacity = gch->get_gen(0)->capacity_before_gc();
  return    (word_size > heap_word_size(gen0_capacity))//要分配的大小>年輕代容量(eden+from總大小)
         || GC_locker::is_active_and_needs_gc()//某些JNI方法正在被調(diào)用
         || gch->incremental_collection_failed();//最近發(fā)生過一次擔(dān)保失敗
}

從上面的代碼可以看出条霜,一個(gè)對(duì)象要在老年代分配催什,需要滿足下面3中情況中的一種(前提條件:對(duì)象大小 < PretenureSizeThreshold設(shè)置的閾值):

  1. 要分配的大小>年輕代容量(eden+from總大小)
  2. 某些JNI方法正在被調(diào)用
  3. 最近發(fā)生過一次擔(dān)保失敗

當(dāng)分配bytes7時(shí)蛔外,不滿足上面任何一種情況,因此bytes7不可能在老年代分配

Third

去掉注釋掉的代碼前面的斜杠溯乒,執(zhí)行程序夹厌,使用HSDB查看不同分區(qū)的內(nèi)存地址,查看bytes7對(duì)象的地址裆悄,確定bytes7對(duì)象所在的內(nèi)存區(qū)域矛纹。
關(guān)于HSDB的使用可以參考R大的博客:http://rednaxelafx.iteye.com/blog/1847971

差不多了,寫完趕快跑路光稼。

自我介紹

我是何勇或南,現(xiàn)在重慶豬八戒,多學(xué)學(xué)02晒弧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冰垄,一起剝皮案震驚了整個(gè)濱河市蹬癌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虹茶,老刑警劉巖逝薪,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蝴罪,居然都是意外死亡董济,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門要门,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)虏肾,“玉大人,你說我怎么就攤上這事欢搜⊙ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵狂巢,是天一觀的道長(zhǎng)撑毛。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么藻雌? 我笑而不...
    開封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任雌续,我火速辦了婚禮,結(jié)果婚禮上胯杭,老公的妹妹穿的比我還像新娘驯杜。我一直安慰自己,他們只是感情好做个,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開白布鸽心。 她就那樣靜靜地躺著,像睡著了一般居暖。 火紅的嫁衣襯著肌膚如雪顽频。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天太闺,我揣著相機(jī)與錄音糯景,去河邊找鬼。 笑死省骂,一個(gè)胖子當(dāng)著我的面吹牛蟀淮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钞澳,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼怠惶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了轧粟?” 一聲冷哼從身側(cè)響起甚疟,我...
    開封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逃延,沒想到半個(gè)月后览妖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揽祥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年讽膏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拄丰。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡府树,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出料按,到底是詐尸還是另有隱情奄侠,我是刑警寧澤,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布载矿,位于F島的核電站垄潮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弯洗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一旅急、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧牡整,春花似錦藐吮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至沐扳,卻和暖如春泥从,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背迫皱。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工歉闰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辖众,地道東北人卓起。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像凹炸,于是被迫代替她去往敵國(guó)和親戏阅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361

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

  • 通過這篇文章你能知道的問題: 1.如何判斷對(duì)象是活著還是死去啤它? 2.在Java語(yǔ)言中奕筐,可作為GCRoots的對(duì)象有...
    beneke閱讀 1,355評(píng)論 0 1
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司变骡,掛了不少离赫,但最終還是拿到小米、百度塌碌、阿里渊胸、京東、新浪台妆、CVTE翎猛、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,281評(píng)論 11 349
  • JVM架構(gòu) 當(dāng)一個(gè)程序啟動(dòng)之前,它的class會(huì)被類裝載器裝入方法區(qū)(Permanent區(qū))接剩,執(zhí)行引擎讀取方法區(qū)的...
    cocohaifang閱讀 1,673評(píng)論 0 7
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理切厘,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 15,632評(píng)論 3 83
  • Java 虛擬機(jī)有自己完善的硬件架構(gòu), 如處理器懊缺、堆棧疫稿、寄存器等,還具有相應(yīng)的指令系統(tǒng)。JVM 屏蔽了與具體操作系...
    尹小凱閱讀 1,695評(píng)論 0 10