资讯专栏INFORMATION COLUMN

Java中的逃逸分析

jayce / 2073人阅读

摘要:当一个对象被定义之后,可能会被外部对象引用,称之为方法逃逸也有可能被其他线程所引用,称之为线程逃逸。在编译过程中,经过逃逸分析确定一个对象不会被其他线程或者方法访问,那么会将对象的创建替换成为多个成员变量的创建,称之为标量替换。

1.引言

Java 程序运行时,JVM 会将 .class 字节码转换成机器能够识别的指令,指令转换过程会产生耗时,延缓程序的运行速度,为了解决这种问题出现了「JIT(即时编译)」技术。JIT 主要有两个功能:

缓存「Hot Spot Code(热点代码:频繁运行的方法或代码块)」对应的机器指令,方便下次调用。

代码编译优化。

而在 JIT 的代码优化过程中,最重要的就是「逃逸分析(Escape Analysis)」。

2. 逃逸分析

逃逸分析就是 分析Java对象的动态作用域。当一个对象被定义之后,可能会被外部对象引用,称之为「方法逃逸」;也有可能被其他线程所引用,称之为「线程逃逸」。

public class EscapeObject {
    public static String createStr() {
        String sb = "hello world!";
        return sb;
    }
}

例如上面这段代码将创建的字符串对象 sb 返回,这样可以被其他方法或线程引用。

public class EscapeObject {
    public static String createStr() {
        StringBuffer sb = new StringBuffer("hello world!");
        return sb.toString();
    }
}

如果这样实现的话,sb 对象就没有「逃逸」。

利用逃逸分析,编译器可以对代码做如下优化:

同步省略

标量替换

栈上分配

2.1 同步省略

在 JIT 编译过程中,如果发现一个对象不会被多线程访问,那么针对这个对象的同步措施就可以省略掉,即「锁销除」。例如 Vector 和 StringBuffer 这样的类,它们中的很多方法都是有锁的,当某个对象确定是线程安全的情况下,JIT编译器会在编译这段代码时进行锁销除来提升效率。

2.2 标量替换

「标量(Scalar)」是指无法再分解成更小粒度的数据,例如 Java 中的原始数据类型(int,long等),相对如果一个数据可以继续分解,则称之为「聚合量(Aggregate)」,例如 Java对象。在 JIT 编译过程中,经过逃逸分析确定一个对象不会被其他线程或者方法访问,那么会将对象的创建替换成为多个成员变量的创建,称之为「标量替换」。

public class EscapeObject {

    private static void getUser() {
        User user = new User("张三", 18);
        System.out.println("user name is " + user.name + ", age is " + user.age);
    }

    public static void main(String[] args) {
        getUser();
    }
}

class User {
    String name;
    int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

上面这段代码中,对象 user 只会在getUser()方法中被调用,那么 JIT动态编译时,不会创建对象 user,而之创建它的两个成员变量 name 和 age,类似:

private static void getUser() {
    String name = "张三";
    int age = 18;

    System.out.println("user name is " + user.name + ", age is " + user.age);

}

public static void main(String[] args) {
    getUser();
}

标量替换减少了创建对象需要的堆内存,同时也不用进行 GC。

2.3 栈上分配

「栈上分配」是指对象和数据不是创建在堆上,而是创建在栈上,随着方法的结束自动销毁。但实际上,JVM 例如常用的「HotSpot」虚拟机并没有实现栈上分配,实际是用「标量替换」代替实现的。

在 JAVA 中,对象只分配在堆中:

The heap is the runtime data area from which memory for all class instances and arrays is allocated。

堆是所有的对象实例以及数组分配内存的运行时数据区域。
2.4 如何开启逃逸分析

可以通过设置 JVM 参数来开启或关闭逃逸分析

-XX:+DoEscapeAnalysis :开启逃逸分析(从JDK1.7开始默认开启)

-XX:-DoEscapeAnalysis :关闭逃逸分析

3. 参考资料

深入理解Java中的逃逸分析

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71862.html

相关文章

  • 面试问我 Java 逃逸分析,瞬间被秒杀了。。

    摘要:记得几年前有一次栈长去面试,问到了这么一个问题中的对象都是在堆中分配吗说明为什么当时我被问得一脸蒙逼,瞬间被秒杀得体无完肤,当时我压根就不知道他在考什么知识点,难道对象不是在堆中分配吗最后就没然后了,回去等通知了。。 记得几年前有一次栈长去面试,问到了这么一个问题: Java中的对象都是在堆中分配吗?说明为什么! 当时我被问得一脸蒙逼,瞬间被秒杀得体无完肤,当时我压根就不知道他在考什么...

    CastlePeaK 评论0 收藏0
  • Java对象分配简要流程

    摘要:在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集系统的压力将会小很多。相关参数设置大对象直接进入年老代的阈值,当对象大小超过这个值时,将直接在年老代分配。 jvm系列 垃圾回收基础 JVM的编译策略 GC的三大基础算法 GC的三大高级算法 GC策略的评价指标 JVM信息查看 GC通用日志解读 jvm的card t...

    zorro 评论0 收藏0
  • JVM(1)---虚拟机在运行期的优化策略

    摘要:被多次执行的循环体。数组范围检查消除。这种安全检查策略可以避免溢出。不过,虚拟机还是挺聪明的,它会根据运行期收集到的信息来自动选择最优方案。 1.解释器与JIT编译器 首先我们先来了解一下运行在虚拟机之上的解释器与JIT编译器。 当我们的虚拟机在运行一个java程序的时候,它可以采用两种方式来运行这个java程序: 采用解释器的形式,也就是说,在运行.class运行的时候,解释器一边...

    LiuZh 评论0 收藏0
  • 【JVM从小白学成大佬】2.Java虚拟机运行时数据区

    摘要:虚拟机在执行程序的过程中会把它所管理的内存划分为若干个不同的数据区域。栈帧栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧的概念结构如下运行时数据区脑图高 这里我们先说句题外话,相信大家在面试中经常被问到介绍Java内存模型,我在面试别人时也会经常问这个问题。但是,往往都会令我比较尴尬,我还话音未落,面试者就会背诵一段(Java虚拟...

    shuibo 评论0 收藏0
  • JVM的编译策略

    摘要:解释器与编译器并存如果选用完全解释策略,那么编译器将停止所有的工作,字节码将完全依靠解释器逐行解释执行。如果选用完全编译策略,那么解释器仍然会在编译器无法进行的特殊情况下介入运行,这主要是确保程序能够最终顺序执行。 jvm系列 垃圾回收基础 JVM的编译策略 GC的三大基础算法 GC的三大高级算法 GC策略的评价指标 JVM信息查看 GC通用日志解读 jvm的card table数据...

    CloudwiseAPM 评论0 收藏0

发表评论

0条评论

jayce

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<