什么是JVM中的逃逸分析?

82 阅读3分钟

在java中,我们创建的对象一般都是存放在堆内存当中的,堆内存不足时,GC会对堆内存进行垃圾回收,如果GC次数过于频繁,这就会影响程序的性能,所以这才有了逃逸分析,逃逸分析的目的就是判断哪些对象是可以存放在栈内存当中,而不用存放在堆内存当中的,从而让其随着线程的消逝而消逝,进而减少GC发生的频率。

1.定义

逃逸分析总得来说就是,jvm可以分析新创建对象的使用范围,并决定是否在java堆上分配内存的一项技术。在方法中创建对象之后,如果这个对象除了在本方法体中还在其她地方被引用了,此时如果该方法执行结束,由于该对象还有其它地方被引用着,那么GC是不能回收该对象的,此时便称为内存逃逸现象。(逃逸,你可以理解成这个对象逃逸出这个方法体)。一个对象如果并没有逃逸出方法体,那么就有可能被优化成栈上分配,这样就无需在堆上分配内存,也无需进行垃圾回收了(因为方法运行结束,方法对应的栈帧就出栈了,栈帧里面的内容就销毁了,这个分配到该栈帧的对象就消失了,不用像堆内存那样需要GC了)。

2.如何开启逃逸分析

‐XX:+DoEscapeAnalysis //表示开启逃逸分析 (jdk1.8默认开启),虚拟机将尝试在栈内存分配未逃逸的对象,减少堆内存使用并优化垃圾回收(GC)。
‐XX:‐DoEscapeAnalysis //表示关闭逃逸分析,所有对象默认分配到堆内存,栈内存不参与对象分配。

3.逃逸分析的类型

3.1 方法逃逸

如果方法内的对象被方法外的代码引用,则该对象发生方法逃逸。代码示例:

public class MethodEscape {
    private Object obj;
    public void methodEscape() {
        //对象逃逸到当前方法外
        obj = new Object();
    }
}

3.2线程逃逸

如果对象被当前线程以外的代码引用,则该对象发生线程逃逸。代码示例:

public class ThreadEscape {
    public void threadEscape(){
        //这里新创建了一个线程
        new Thread(() -> {
            //对象逃逸到当前线程外
            System.out.println(new Object());
        }).start();
    }
}

4.逃逸分析的优化方式

4.1栈上分配

如果一个对象没有发生逃逸,则可以将该对象分配在栈上。这样做的好处就是减少堆内存分配,降低垃圾回收的压力。代码示例:

public void stackAlloc(){
    //对象没有逃逸,将对象分配在栈上
    Object obj = new Object();
}

我们再来看一些代码例子:

public class EscapeAnalysis {

    public static Object globalObject;

    public Object obj;

    public void globalEscape(){
        globalObject = new Object();  // globalObject为静态变量,外部线程可见,发生逃逸
    }

    public void objEscape(){
        obj = new Object();  // 赋值给堆中实例字段,外部线程可见,发生逃逸
    }
    
    public Object returnObjectEscape(){
        return new Object();   // 返回实例,外部线程可见,发生逃逸
    }

    public void noEscape(){
        Object noEscape = new Object();   // 仅创建线程可见,对象无逃逸
    }

}

5.逃逸分析测试

我们写一个测试代码,for循环一亿次,循环体内调用allot()方法,allot()方法内部就写了个new Student对象的逻辑,这个Student对象是内部的,没有逃逸,所以从理论上来说JVM会进行优化。我们来对比一下开启和关闭逃逸分析之后各自程序的运行时间:

5.1开启逃逸分析

因为JDK1.8是默认开启逃逸分析的,所以直接运行程序即可。

image-20250920233511603.png

image-20250920233852954.png

5.2关闭逃逸分析

image.png

image-20250920233428586.png

image-20250920233737205.png

总结:无论是从代码的执行时间,还是从堆内存中对象的数量(十三万个 VS 一千多万个)来分析,在上述场景下,开启逃逸分析都是有正向收益的。