内存分配策略

JVM

Java自动内存管理:给对象自动分配内存以及回收分配给对象的内存。

对象的内存分配,在大的方向上来看实在堆上进行分配,对象主要分配在Eden区上,如果启动了线程本地分配缓冲,则优先在TLAB上进行分配。少数的情况下直接分配到老年代。分配的规则并不是百分百固定的,其细节取决于当前的垃圾收集器采用的是哪一种的组合,还有虚拟机跟相关参数的设置。

对象优先在Eden区分配

大多数的情况下对象在Eden区进行分配,当Eden区没有足够的空间的时候会触发一次Monitor GC.

Monitor GC,发生在新生代的垃圾收集动作,由于新生代的对象具有朝生夕死的特性,所以新生代的GC非常频繁,而且速度比较快。

Full GC(Major GC),发生在老年代的GC,一般会伴随着一次Monitor GC,速度比Monitor GC 慢很多(10倍以上)。

大对象直接进入老年代

这里所谓的大对象是指需要很大的连续内存空间的对象,最典型的大对象是指那种很长的字符串跟数组。经常出现大对象,容易导致内存还有很多的空间的时候,不得不触发一次垃圾收集动作,来获取足够的连续的内存空间。比大对象更加坏的消息,是遇到一群朝生夕灭的短命大对象,在写程序时候应该进行避免。

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

对象有一个年龄计数器,如果对象在Eden出生并在发生一次Monitor GC后仍然存活,而且Survivor空间能够容纳他的话,他就会被放入Survivor空间同时年龄增加一,此后没发生一次Monitor GC 对象的年龄都会增加一岁,当对象的年龄增加到一定的岁数后,就会被放入老年代。

动态对象年龄判断

为了更好的适应不同的内存的情况,虚拟机并不是永远的要求对象的年龄达到某个最大值的时候才会进入老年代,如果Survivor空间中,相同年龄的对象的总和大于Survivor空间的一半,那么年龄大于等于这个年龄的对象就会进入老年代,而不必等到年龄到达某个最大值。

空间分配担保

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

  • 如果大于,则此次Minor GC是安全的

  • 如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
    如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

上面提到了Minor GC会有风险,是因为新生代采用复制收集算法,因为新生代的对象的生命周期比较短,为了内存利用率,并没有按照1:1的比例来划分内存空间,而是将内存空间划分成一块较大的Eden空间和两块较小的Survivor空间(HotSpot虚拟机默认Eden和Survivor的大小比例是8:1。),每次使用Eden和其中的一块Survivor。当回收的时候,将Eden和Survivor上还存活的对象一次性的复制到另一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后新生代中所有对象均存活),而Survivor空间是比较小的,这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。

取平均值仍然是一种概率性的事件,如果某次Minor GC后存活对象陡增,远高于平均值的话,必然导致担保失败,如果出现了分配担保失败,就只能在失败后重新发起一次Full GC。虽然存在发生这种情况的概率,但大部分时候都是能够成功分配担保的,这样就避免了过于频繁执行Full GC。所以大多数情况,为了避免频繁的Full GC,会将HandlePromotionFailure开关打开。

关于新生代空间的划分,建议阅读:java堆,新生代,老年代,Eden空间,From Survivor空间,To Survivor空间

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器