从GC角度来看,堆内存的比例

1,921 阅读6分钟

对象是怎么进入老年代的

(1)躲过15次GC之后进入老年代

      我们写的系统刚启动的时候,创建的各种各样的对象,都是分配在新生代里的。随着系统的运行,新生代就满了,此时就会触发Minor GC,可能就少量存活对象转移到空着的Survivor区中,存活类对象每次在新生代里躲过一次GC被转移到一块Survivor区域中,此时他的年龄就会增长一岁。默认的设置下,当对象的年龄达到15岁的时候,也就是躲过15次GC的时候,他就会转移到老年代里去。

可以通过JVM参数“-XX:MaxTenuringThreshold”来设置,默认是15岁

(2)动态对象年龄判断

      假如说当前放对象的Survivor区域里,一批对象的总大小大于了这块Survivor区域的内存大小的50%,那么此时大 于等于这批对象年龄的对象,就可以直接进入老年代了。

      如果Age1 + Age2 +Age3 > Survivor*50%,那么Age3、Age4、Age4++以上的对象将直接进入老年代(这样看起来可能好理解)

为什么要有动态对象年龄判断呢?

       回到划分新生代老年代的初心,是为了 让可能 长期存活的对象 尽早进入老年代。这句话听起来很难理解,后面在分析场景的时候,会一步一步带大家理解。

(3)大对象直接进入老年代

         有一个JVM参数,就是“-XX:PretenureSizeThreshold”,可以把他的值设置为字节数,比如“1048576”字节,就是1MB。他的意思就是,如果你要创建一个大于这个大小的对象,比如一个超大的数组,或者是别的啥东西,此时就直接把这个大对象放到老年 代里去。压根儿不会经过新生代。 

大对象为什么要直接进入老年代呢?

       之所以这么做,就是要避免新生代里出现那种大对象,然后屡次躲过GC,还得把他在两个Survivor区域里来回复制多次之后才能进入 老年代。大的一个对象在内存里来回复制,不是很耗费时间吗?所以说,这也是一个对象进入老年代的规则

      但如果确定自己系统中的大对象涉及的处理速度很快,那么建议适当调大 大对象的定义,调大数值,防止触发老年代GC

(4)老年代空间分配担保规则

      如果新生代里有大量对象存活下来,确实是自己的Survivor区放不下了,这个时候就必须得把这些对象全部 直接转移到老年代去。

那么在YGC的时候,会出现哪几种情况呢?

       第一种可能(存活对象 < Survivor),Minor GC过后,剩余的存活对象的大小,是小于Survivor区的大小的,那么此时存活对象进入Survivor 区域即可。 

      第二种可能(存活对象 > Survivor && 存活对象 < Old),Minor GC过后,剩余的存活对象的大小,是大于 Survivor区域的大小,但是是小于老年代可用内存大小 的,此时就直接进入老年代即可。 

      第三种可能(存活对象 > Survivor && 存活对象 > Old),很不幸,Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内 存的大小。此时老年代都放不下这些存活对象了,就会发生“Handle Promotion Failure”的情况,这个时候就会触 发一次“Full GC”。 

       第四种可能,如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的 “OOM”内存溢出了


进入老年代的几种情况我们基本已经了解了,那么我们了解这些概念有什么价值呢?大家可以思考一下

        在上一篇文章中,我们提到YGC和FullGC时的比例,所以我们希望尽可能的避免触发fullGC,来减小 GC对用户响应时间的影响。在了解了上述内容之后,我们就可以通常调整堆内存的比例,来防止一些非长期存活的对象,进入老年代。接下来我们来结合上面的情况,进一步来分析


内存模型对GC的影响,如何调优



先回顾一下堆内存各空间比例,eden:s0:s1 = 8:1:1,之后我们便来按照上面的几种情况,一个一个分析:

(1)躲过15次GC之后进入老年代

       在我们常接触的web系统中,通常请求处理都比较快,通常都不会熬过几次GC,而长期存活的对象会在这个过程中多次被复制,对性能是有损耗的,鉴于这种场景,建议 调小JVM参数“-XX:MaxTenuringThreshold”,方便长期存活对象,尽快进入老年代。

(2)动态对象年龄判断

        首先,我们要识别出我们的系统,哪些场景会触发动态年龄判断,动态年龄判断是我们希望的,还是我们要避免的;

如果是我们要避免的,我们该如何避免呢

        要解决这个问题,我们需要回到前面的概念,触发的条件是 大于survivor的50%,那么我们可以survivor区域内存的大小即可。调整方式有两种:

1、增大survivor区域在新生代中内存的占比,调整JVM参数“-XX:SurvivorRatio“;如 新生代:老年代 = 2:1

2、增大新生代内存大小,调整JVM参数“-XX:NewRatio”。 如eden:s0:s1 = 6:2:2

(3)大对象直接进入老年代

      和上面一下要识别次对象是否是我们所希望进入老年代的,然后通过调整JVM参数“-XX:PretenureSizeThreshold”来控制进入老年代

(4)老年代空间分配担保规则

       这种情况是我们不希望发生的,因为会有大量短期存活对象进入老年代,我们要尽可能的避免。关键还是survivor太小,无法存放GC之后的存活对象,调优策略:

1、增大新生代对象在内存中的比例,这样间接增大了survivor空间的大小;

2、增大survivor区域在新生代中内存的占比

总结

       因为FullGC性能低,耗时较长,所以我们希望尽量避免fullGC,为了这个目的,我们需要了解哪些对象,在哪些时候会进入老年代。进而识别出哪些对象是应该进入老年代的,哪些对象是不应该进入老年代的,来调整内存分配比例,最终达到避免fullGC的效果。