对象的分配策略

对象的分配原则

在这里插入图片描述

对象优先在Eden区分配

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

大对象直接进入老年代

大对象就是指需要大量的连续内存空间的Java对象,最典型的大对象便是那种很长很长的字符串,或者元素数量庞大的数组。
大对象对虚拟机的内存分配来说就是一个不折不扣的坏消息,比遇到一个大对象更坏的消息是一群朝生夕灭短命大对象,写程序一定要避免。
为什么要避免大对象,因为在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好他们,而当复制对象时,大对象就意味着高额的内存复制开销。
HotSpot 提供了-XX:PretenureSizeThreshold 参数,指定大于该设置值的对象直接在老年代分配 ,这样做的目的:
1、避免大量内存复制(避免Eden区及两个survivor区之间来回复制,产生大量的内存复制操作)
2、避免提前进行垃圾回收。

长期存活对象进入老年区

HotSpot 虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存活的对象应当放在新生代,哪些存活的对象存放下老年代。
为了坐到这点,虚拟机给每个对象定义了一个对象年龄计数器(Age),存储在对象头中。
在这里插入图片描述
在这里插入图片描述
如果对象在Eden 出生并经过第一次MInor GC 后仍然存活,并且被Survivor 区容纳的话,将被移动到 Survivor 空间中,并将年龄加一,对象在 Survivor区每经过一次 Minor GC,年龄就增加 1,当它的年龄增加到一定程度(并发的垃圾回收器默认为 15),CMS 是 6 时,就会被晋升到老年代中。
-XX:MaxTenuringThreshold 参数可以调整

对象年龄动态判断

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

空间分配担保

在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么 Minor GC 可以确保是安全 的。如果不成立,则虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历 次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC,尽管这次 Minor GC 是有风险的,如果担保失败则会进行一次 Full GC;如果小 于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 Full GC。

逃逸分析

逃逸分析的原理

分析对象动态作用域,当一个对象在方法中定义后,它可能被外部方法所引用。
比如:调用参数传递到其他方法中,这种称之为方法逃逸。甚至还有可能被外部线程访问到,例如:赋值给其他线程中访问的变量,这个称之为线程逃逸。 从不逃逸到方法逃逸到线程逃逸,称之为对象由低到高的不同逃逸程度。 如果确定一个对象不会逃逸出线程之外,那么让对象在栈上分配内存可以提高 JVM 的效率。

上代码

public class Test {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000000; i++) {
            allocate();
        }
        System.out.println(System.currentTimeMillis() - start +"毫秒");
        Thread.sleep(600000);
    }

    static void allocate() {
        MyOject myOject = new MyOject(2021, 2021.1314);
    }

    static class MyOject {
        int a;
        double b;

        MyOject(int a, double b) {
            this.a = a;
            this.b = b;
        }

    }

}

-XX:-DoEscapeAnalysis (关闭逃逸分析)
执行结果:
在这里插入图片描述
-XX:+DoEscapeAnalysis (开启逃逸分析,默认开启)
执行结果:
在这里插入图片描述
测试结果可见,开启逃逸分析对代码的执行性能有很大的影响!那为什么有这个影响?

如果是逃逸分析出来的对象可以在栈上分配的话,那么该对象的生命周期就跟随线程了,就不需要垃圾回收,如果是频繁的调用此方法则可以得到很大的性能提高。 采用了逃逸分析后,满足逃逸的对象在栈上分配

在这里插入图片描述
没有开启逃逸分析,对象都在堆上分配,会频繁触发垃圾回收(垃圾回收会影响系统性能),导致代码运行慢
在这里插入图片描述
上代码(上面同样代码)
开启逃逸分析-XX:+DoEscapeAnalysis ,开启 GC 打印日志 -XX:+PrintGC

运行结果:

在这里插入图片描述
没有GC 日志,说明没有触发垃圾回收

关闭逃逸分析-XX:-DoEscapeAnalysis ,开启 GC 打印日志 -XX:+PrintGC

在这里插入图片描述
可以看到关闭了逃逸分析,JVM 在频繁的进行垃圾回收(GC),正是这一块的操作导致性能有较大的差别。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值