遇到一个对象分配的问题

最近看了毕玄老师分享的的PPT,对其中一个案例进行了测试,参考代码如下:

/**
 * -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];
        }
    }
}

运行的代码和参数如上,在复杂代码到机器里面运行之前,请大家思考下面2个问题:

  1. bytes7对象能否分配成功?
  2. 如果bytes7能够分配成功,应该分配在哪里呢?

如果大家对于对象的分配流程感兴趣,可以参考我的博文:

对象的创建以及YoungGC的触发:http://www.jianshu.com/p/941fe93d21c2
JVM之逃逸分析以及TLAB:http://www.jianshu.com/p/3835450d49d0

好了言归正传,回答上面的问题!

bytes7对象能否分配成功

上面的对象能够分配成功,在 eclipse 执行上面的程序,打印出来的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日志,从第一条日志可以分析出,创建bytes4的时候,年轻代不足,触发了一次YoungGC,年轻代存活对象大小 = 6M,To Survivor 区大小 = 1M,To区空间不够,通过空间担保机制晋升到老年代,老年代的晋升规则如下:

bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
  //老年代最大连续空间
  size_t available = max_contiguous_available();
  //平均晋升对象的大小
  size_t av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average();
  //最大连续空间大小 > 平均大小或最大连续空间 > 晋升对象的大小
  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;
}

从上面的代码可以看出,如果最大连续空间大小 > 平均大小或最大连续空间 > 晋升对象的大小,就运行年轻代对象晋升到老年代,该程序中老年代空间 = 10m,因此可以将年轻代对象晋升到老年代。
下面继续看第二条日志,发现年轻代在分配bytes7对象的时候,年轻代不足触发YoungGC,但是老年代空间不足,无法满足晋升规则,因此触发了一次FullGC,部分年轻代对象被晋升到老年代。从源码角度分析一下为什么会触发FullGC,源码如下:

//表示有jni在操作内存,此时不能进行GC避免改变对象在内存的位置
if (GC_locker::is_active_and_needs_gc()) {
    if (!gch->is_maximal_no_gc()) {
      result = expand_heap_and_allocate(size, is_tlab);//扩堆
    }
    return result;   // could be null if we are out of space
    //consult_young=true的时候,表示调用该方法时,判断此时晋升是否的安全的。
    //若=false,表示只取上次young gc时设置的参数,此次不再进行额外的判断。
  } else if (!gch->incremental_collection_will_fail(false /* don't consult_young */)) {
    // 对年轻代进行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 :: ");
    }
    //对老年代进行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;
  }
  //如果对象还是没有分配成功,进行一次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 */);

从上面的代码中可以看出如果进行YoungGC分配失败以后,会进行一次FullGC,进行对象的回收,从日志可以看出,在进行FullGC时,部分年轻代对象晋升到了老年代。

如果bytes7能够分配成功,应该分配在哪里呢?

重新第二条日志,老年代区内存使用量增加了2M,有人可能会问老年代区增加的 2M 也可能是 bytes7 在老年代分配了啊?这里提供 3 种方法证明 bytes7 对象在年轻代分配

First

在上面的程序中将 :

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

修改为:

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

执行上面的程序,查看日志:

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

发现年轻代使用量 = 8016K = 7.8 M,老年代使用量 = 8192K = 8M,bytes7分配了3M的内存,因此可以证明bytes7 是在年轻代分配的。

Second

光从日志的角度还是不能证明,下面我们从源码角度去证实一下(最好是先看:《对象的创建以及YoungGC的触发》这篇文章):
在年轻代进行对象分配的时候,会调用 GenCollectorPolicy::should_try_older_generation_allocation 方法来判断对象是否在老年代分配,具体实现如下:

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方法正在被调用
         || gch->incremental_collection_failed();//最近发生过一次担保失败
}

从上面的代码可以看出,一个对象要在老年代分配,需要满足下面3中情况中的一种(前提条件:对象大小 < PretenureSizeThreshold设置的阈值):

  1. 要分配的大小>年轻代容量(eden+from总大小)
  2. 某些JNI方法正在被调用
  3. 最近发生过一次担保失败

当分配bytes7时,不满足上面任何一种情况,因此bytes7不可能在老年代分配

Third

去掉注释掉的代码前面的斜杠,执行程序,使用HSDB查看不同分区的内存地址,查看bytes7对象的地址,确定bytes7对象所在的内存区域。
关于HSDB的使用可以参考R大的博客:http://rednaxelafx.iteye.com/blog/1847971

差不多了,写完赶快跑路。

自我介绍

我是何勇,现在重庆猪八戒,多学学!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 通过这篇文章你能知道的问题: 1.如何判断对象是活着还是死去? 2.在Java语言中,可作为GCRoots的对象有...
    beneke阅读 1,350评论 0 1
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,182评论 11 349
  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,646评论 0 7
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,538评论 3 83
  • Java 虚拟机有自己完善的硬件架构, 如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系...
    尹小凯阅读 1,678评论 0 10