内存分配策略

一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?这和两个方面有关:对象的类型和在 Java 类中存在的位置。

Java的对象可以分为基本数据类型和普通对象。

  • 对于普通对象来说,JVM会首先在堆上创建对象,然后在其它地方使用的其实是它的引用。比如把这个引用保存在虚拟机栈的局部变量表中。
  • 对于基本数据类型来说(byte、short、int、long、float、double、char)有两种情况。当你在方法体内声明了基本数据类型的对象,就会在栈上直接分配;其他情况都是在堆上分配。

但是随着JIT编译器的发展与逃逸分析技术的成熟,栈上分配标量替换优化也会使我们的对象可能在栈上分配。
在这里插入图片描述

一、栈上分配

那么什么情况下,我们的对象会在栈上分配呢?就是我们的逃逸分析技术发现这个对象没有逃逸出一个方法,那么这个对象就是可以在栈上分配的,这样我们的对象就会在栈内,它会随着我们方法的结束会消亡,这样就不需要我们的GC进行回收的,它会随着方法自行销毁,可以提升性能。

逃逸分析的原理: 分析对象动态作用域,当一个对象在方法中定义后,它可能被外部方法所引用。
比如:调用参数传递到其他方法中,这种称之为方法逃逸。甚至还有可能被外部线程访问到,例如赋值给其他线程中访问的变量,这个称之为线程逃逸。 从不逃逸到方法逃逸到线程逃逸,称之为对象由低到高的不同逃逸程度。

如果确定一个对象不会逃逸出线程之外,那么让对象在栈上分配内存可以提高 JVM 的效率。

public class Client {
    
    

    public User newUser(){
    
    
        User user = new User();
        user.setName("Rocky");
        user.setAge(18);
        return user;
    }
    
    public static class User{
    
    
        private String name;
        private int age;

        //省略Getter、Setter方法
        ...
    }
}

上述我们newUser方法中的对象user就算逃逸了,就无法在栈上进行分配,只能在堆上分配,因为在newUser方法中,user对象最后被返回出去了,意味着就是由别的方法可以获取这个user对象。


下面我再来看看没有逃逸的,就是对象可以在栈上进行分配的,如下

public class Client {
    
    

    public void test(){
    
    
        User user = new User();
        user.setName("Rocky");
        user.setAge(18);
    }

    public static class User{
    
    
        private String name;
        private int age;

        //省略Getter、Setter方法
        ...
    }
}

这里我们就来测试一下我们的使用和不使用逃逸分析技术对性能的差异,所以先看下列测试代码

public class Client {
    
    

    public static class User {
    
    
        private String name;
        private int age;

        //省略Getter、Setter方法
        ...
    }

    public static void newUser() {
    
    
        User user = new User();
        user.setName("Rocky");
        user.setAge(18);
    }

    public static void main(String[] args) {
    
    
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
    
    
            newUser();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:" + (endTime - startTime) + "毫秒");
    }
}

接下来我们要运行我们的测试代码,这里再运行之前,我们需要设置一些参数
在这里插入图片描述
-server JVM运行的模式之一,server模式才能进行逃逸分析,JVM运行的模式还有 mix / client
-Xmx10m 设置堆的最小值为10m
-Xms10m 设置堆的最大值为10m
-XX:+DoEscapeAnalysis 启用逃逸分析(默认打开)
-XX:+PrintGC 打印GC日志
-XX:+EliminateAllocations 标量替换(默认打开)
-XX:-UseTLAB 关闭本地线程分配缓冲
在这里插入图片描述

使用了逃逸分析技术,在栈上进行分配对象,这里花费了3毫秒,那么我们再来关闭逃逸分析技术看一看,我们只需将我们的配置的参数中的启用逃逸分析关闭即可。
在这里插入图片描述
在这里插入图片描述
这里在堆上进行分配是,我们GC进行了大量的回收,共花费了1148毫秒。


这里我们再补充一下我们上述配置中提到的标量替换,就是我们在确定了方法中的对象没有逃逸,是可以在方法上进行分配时,JVM不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替,如下:
在这里插入图片描述


二、对象优先在Eden区分配

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


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

所谓的大对象是指,需要大量连续内存空间的Java对象,最经典的大对象就是那种很长的字符串,或者元素数量很庞大的数组。在Java虚拟机中要避免大对象的原因是,在分配空间时存在以下缺点:

  • 容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好 它们
  • 当复制对象时,大对象就意味着高额的内存复制开销

虚拟机也提供了 -XX:PretenureSizeThreshold 参数(仅对Serial和ParNew垃圾收集器有效),指定大于该设置值的对象直接在老年代分配,缺省为0 ,表示绝不会直接分配在老年代。这样做的目的如下:

  • 避免提前进行垃圾回收,明明内存有空间可以进行分配
  • 避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作

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

HotSpot虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存活对象应当放在新生代,哪些存活对象放在老年代中。 为做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器,存储在对象头中。
在这里插入图片描述

如果对象在Eden区出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间,并且对象的年龄设为1,对象在Survivor每经过一次Minnor GC,年龄就会增加1岁,当它的年龄增加到一定程度(默认为15岁),就将晋升到老年代。对象晋升到老年代的年龄阈值,可以通过-XX:MaxTenuringThreshold设置(最大为15岁,因为在对象头Mark Word中采用4个bit位来保存年龄,4个bit位能表示的最大数就是15)。


五、动态对象年龄判定

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


六、空间分配担保

新生代中有大量的对象存活,Survivor空间不够,当出现大量对象在MinorGC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。


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

猜你喜欢

转载自blog.csdn.net/rockvine/article/details/124615040
今日推荐