JVM学习笔记之内存分配与回收策略【六】

2021-02-28  本文已影响0人  JiaJianHuang

内存分配与回收策略

以下例子使用 openjdk8 测试

一、对象优先在 Eden 分配

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

/**
 * @Description: 优先分配Eden
 * <p>
 * 虚拟机参数: 使用Serial加SerialOld客户端默认收集器组合下的内存分配和回收的策略(-XX:+UseSerialGC)
 * <pre>
 *     -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
 * </pre>
 *
 * @Author hdj
 * @Date 2021/1/10 下午5:01
 */
public class AllocationEden {

    public static final int _1M = 1024 * 1024;
    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3, allocation4;

        allocation1 = new byte[2 * _1M];
        allocation2 = new byte[2 * _1M];
        allocation3 = new byte[2 * _1M];
        //出现一次minor GC
        allocation4 = new byte[4 * _1M];
    }
}

从 GC 的日志可以看出 新生代总内存:9216K,已使用:4647K

二、大对象直接进入老年代

大对象就是指需要大量连续内存空间的 Java 对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组。大对象对虚拟机的内存分配来说就是一个不折不扣的坏消息,比遇到一个大对象更加坏的消息就是遇到一群“朝生夕灭”的“短命大对象”,我们写程序的时候应注意避免。在 Java 虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,而当复制对象时,大对象就意味着高额的内存复制开销。

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

HotSpot 虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存活对象应当放在新生代,哪些存活对象放在老年代中。为做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器,存储在对象头中。对象通常在 Eden 区里诞生,如果经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,该对象会被移动到 Survivor 空间中,并且将其对象年龄设为 1 岁。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过以下虚拟机参数设置。

对于上面的代码,不管设置-XX:MaxTenuringThreshold=1 或者 15,都在第二次 Minor GC 后,都进入了老年代。

这里就有个疑问了,不是设置-XX:MaxTenuringThreshold 进入老年代的阀值吗,为什么不起作用呢?

看了动态对象年龄判定的简单解析,是不是感觉还有一个疑问,分配的 allocate 256K 没有超过 Survivor 空间的一半啊? 下面分析以下内存变化

四、动态对象年龄判定

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

五、空间分配担保

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

参考

上一篇 下一篇

猜你喜欢

热点阅读