首页 > 代码库 > JVM GC 之二对象分配

JVM GC 之二对象分配

      Java体系中的内存自动管理其实是解决两大问题:给对象分配内存和回收分配个对象的内存。
     一般情况下对象是在堆上分配(但也可能是经过JIT(Java即时编译)编译后被拆散为标量类型并间接的在栈上分配),对象主要是分配在新生代中的Eden区,如果启动了本地线程分配缓冲,将按线程优先分TLAB上。也可以通过配置 -XX:PretenureSizeThreshold参数直接分配在老年代中。

     英文诗意:Max  Tenuring占有 Threshole阀值

     对象有限分配在Eden区
     多数情况下,对象在新生代Eden中分配,当Eden区内存不足时,进行一次Minor GC.(年轻代GC)

     可以配置 -XX:PrintGCDetails参数,告诉虚拟机在垃圾收集行为发生时打印内存回收日志,并且在进程退出的时候输出当前内存各区域的分配情况。

     配置参数-Xms20M, -Xmx20M 和 -Xmn10M 设置Java堆大小为20M,且不可扩展,同时设置新生代10M ,剩下10M分配给老年代。

     配置参数 -XX:SurvivorRatio=8 设置新生代中Eden区与一个Survivor空间大小是8:1.

     Minor GC : 新生代GC,发生在新生代的垃圾收集动作,因为新生代中的对象都具备朝生夕死特性,所以Minor GC非常频繁。

     Major GC/Full GC : 老年代GC,发生在老年代中的垃圾收集动作,一般发Full GC之前都会伴随着至少一次的Minor GC. Full GC 会比Minor GC慢10倍以上。
     
     大对象直接进入老年代
     这里所说的大对象是指需要大量连续内存空间的Java对象,比较典型的就是特别长的字符串及数组。经常出现大对象会导致内存还有很多空余就提前触发GC动作,以获取足够的连续空间。

     朝生夕灭的短命大对象会在新生代的Eden中分配,这类对象过多时会引起过多的GC操作:从Eden 、from Survivor 到 to Survivor 两个区域间的复制操作。开发中应当避免生成这样的对象。

     配置 -XX:PretenureSizeThreshold 参数使大于这个阀值的对象直接进入老年代中分配,从而避免在Eden和两个Survivor区间的对象拷贝。备注:新生代中采用复制算法回收内存。 

     长期存活的对象将进入老年代
     虚拟机采用分代收集器的思想来管理内存,分代算法就是: 新生代中的对象都是朝生夕死,采用复制算法,Eden\From Survivor 到 To Survivor 区域中拷贝还活着的对象;老年代中的对象标记-清理和标记-整理算法回收,避免大量活着的大对象来回拷贝。

     那当对象从Eden 、From Survivor 区域中拷贝出去时,是怎么决定对象要放入到 To Survivor中还是老年代中的呢?
     虚拟机给每个对象定义了一个年龄计算器,如果对象经历过一次Minor GC后仍然存活并且能被 To Survivor收容的话,该对象将被移动到Survivor区域中,并把对象年龄设为1。对象在Survivor中每熬过一次Minor GC,年龄就增加一岁,当他的年龄达到一定岁数时(默认15岁),就会晋升到老年代。
     
     配置参数 -XX:MaxTenuringThreshold=8 设置对象8岁时晋升到老年代区间。

     对象年龄动态判定
     如果Sruvivor区间中的所有相同年龄的对象的总和大于Survivor空间的一半,年龄 大于或等于该年龄的对象将直接进入老年代,无需等待 -XX:MaxTenuringThreshole设置的年龄。

     空间分配担保
     发送Minor GC 时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小。如果大于,则改为直接Full GC ; 如果小于,再查看 HandlePromotionFailure设置是否允许担保失败,如果允许,则会进行Minor GC,如果出现HandlePromotionFailure 失败,就会在失败后重新发起一次Full GC ; 如果不允许,则也要改为进行一次Full GC .

     新生代采用复制算法回收内存,为了内存利用率,只使用其中一个Survivor作为轮换备份,因此当Survivor的空间不足以存储MinorGC 存活下来的对象时,就需要老年代进行分配担保。前提是老年代需要有足够的空间进行存储MinorGC存活下来的对象,一共有多少对象会活下来,在实际完成回收之前是无法明确知道的。所以只能取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,如果经验值大于老年代的空间,则需要进行一次Full GC.

     既然是平均值,就会存在实际值大于老年代空间的情况,这时就会出现担保失败,如果出现了担保失败,就会在失败后进行一次Full GC.

      虽然担保失败后再进行Full GC 是绕了一大圈子,但大部分还会把HandlPromotionFailure开关打开,防止Full GC过于频繁。

     垃圾收集器
     Stop The World!当GC开始工作时,必须暂停其他线程。

     并行:多条垃圾收集线程并行工作,但此时用户线程仍然是暂停的。
     并发:用户线程和垃圾收集线程同时执行,但不一定是并行的,可能会交替执行。用户线程继续运行,而垃圾收集线程运行在另一个CPU上。

     






















JVM GC 之二对象分配