内存分配与回收策略

对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的Eden区,如果启动了本地县城分配缓存,将按照线程优先在TLAB上分配,少数情况下也可能会直接分配在老年代中,分配规则并不是百分百固定的,其细节决定于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。

对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将进行一次Minor GC

Minor GC和Full GC有什么不一样吗

-
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

-
老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随着至少一次的Minor GC(但非绝对,在Parallel Scavenge收集器的手机策略里有直接进行Major GC的策略选择过程)Major GC的速度一般会比Minor GC慢10倍以上。

大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的java对象,最典型的大对象就是那种很长的字符串以及数组,大对象对虚拟机的内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续时间来“安置”它们。虚拟机提供了-XX:PretenureSizeThreshold参数,令大于这个设置值的对戏那个直接在老年代分配,这样做的目的是避免在Eden区以及Survivor区之间发生大量的内存复制。

-XX:PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效,Parallel Scavenge的收集器不认识这个参数,Parallel Scavenge收集器一般并不需要设置,如果遇到必须使用这个参数的场合,可以考虑ParNew加CMS的收集器组合。

长期存活的对象将进入老年代

虚拟机为了采用分代收集的思想来管理内存,利用每个对象定义的对象年龄(Age)计数器,如果对象在Eden出生并经历了第一次Minor GC后仍然存活,并且能被Survivor接纳的话,将移动到Survivor空间中,并且对象年龄设为1,对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当他的年龄增加到一定的程度(默认为15岁),就会被晋升到老年代,对象晋升到老年代的年龄阀值,可以通过参数-XX:MaxTenuringThreshold设置。

动态对象年龄判定

为了更好的适应不同的成都的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对戏那个总空间

  • 如果条件成立,那么Minor GC可以确保是安全的。

  • 如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败

    • 如果允许,那么会继续检查老年代最大可用的连续控件是否大于历次晋升到老年代对象的平均大小

      • 如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的
      • 如果小于,或者HandlePromotionFailure设置不允许冒险,那这也要改为进行一次Full GC。

冒险:前面提到过,新生代使用复制收集算法,但为了内存利用率,只是用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。

取平均值进行比较其实仍然是一种动态概率的手段,也就是说,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然会导致担保失败,如果发生了担保失败,那就治好在失败后重新发生一次Full GC,虽然担保失败的时候绕的圈子是最大的,但大部分的时候还是会打开分配担保的,避免Full GC过于频繁。


内存调优

JVM内存调优,主要是减少GC的频率和减少Full GC的次数,Full GC的时候会极大地影响系统的性能。所以在此基础上,更加要关注会导致Full GC的情况。

容易导致Full GC的情况

-
年老代空间不足

  • 分配足够大空间给old gen。

  • 避免直接创建过大对象或者数组,否则会绕过年轻代直接进入年老代。

  • 应该使对象尽量在年轻代就被回收,或待得时间尽量久,避免过早的把对象移进年老代。

  • 方法区的永久代空间不足

    • 分配足够大空间给。
    • 避免创建过多的静态对象。
  • 被显示调用System.gc()

    • 通常情况下不要显示地触发GC,让JVM根据自己的机制实现。

JVM堆内存分配问题讨论

  • 年轻代过小(年老代过大)

    • 导致频繁发生GC,增大系统消耗
    • 容易让普通大文件直接进入年老代,从而更容易诱发Full GC
  • 年轻代过大(年老大过小)

    • 导致年老代过小,从而更容易诱发Full GC
    • GC耗时增加,降低GC的效率
  • Eden过大(survivor过小)

    • Minor GC时容易让普通大文件直接绕过survivor进入年老代,从而更容易诱发Full GC
  • Eden过小(survivor过大)

    • 导致GC频率升高,影响系统性能
  • 调优策略

    • 保证系统吞吐量优先
    • 减少GC暂停时间优先