聊点硬核的逃逸分析技术

写在文章开头

你好,我叫sharkchili,目前还是在一线奋斗的Java开发,经历过很多有意思的项目,也写过很多有意思的文章,是CSDN Java领域的博客专家,也是Java Guide的维护者之一,非常欢迎你关注我的公众号:写代码的SharkChili,这里面会有笔者精心挑选的并发、JVM、MySQL数据库专栏,也有笔者日常分享的硬核技术小文。

逃逸分析技术算是在JVM 面试题偶有提及的一个考察点,当然如果你能够讲解JVM工作原理的时候提及这一点,这一定会增加面试官对你的好感,本文主题内容如下:

通过对本篇文章的阅读,你将能够从容的解决以下几个面试题:

  1. 什么是逃逸分析技术?
  2. 逃逸分析技术解决什么问题?带来什么好处?
  3. 如何更好的理解或者运用逃逸分析技术?

什么是逃逸分析

逃逸分析技术是JVM用于提高性能以及节省内存的手段,在JVM编译语境下也就是我们常说的JIT阶段,逃逸分析技术通过以下两个条件判断该对象是否是逃逸:

  1. 该对象是否分配在堆上(static关键字或者成员变量)。
  2. 该对象是否会传给未知代码,比如return到外部给别的类使用。

只要编译阶段判定当前对象并没有发生逃逸,那么它就会采用栈上分配标量替换同步锁消除等手段提升程序执行性能和节省内存开销。

那么我们又该如何判断对象是否逃逸呢?我们不妨基于上述的判断条件来看看这个示例,假设我们现在有一个user类:

arduino 复制代码
@Data
public class User {

    private int id;

    private String name;
}

我们通过UserService进行初始化,那么请问这段代码是否发生逃逸呢?

csharp 复制代码
public class UserService {

    private User user;

    public void init() {
        user = new User();
        user.setId(RandomUtil.randomInt(10));
        user.setName(RandomUtil.randomString(3));
    }
}

答案当然是肯定的,因为这段代码会被外部的其他任意线程操作。

再来看看这段代码,典型的return语句,很明显的外部线程可以直接操作这个对象,所以这个对象也发生了逃逸,所以针对这几种情况JIT都无法对其进行优化。

sql 复制代码
public User createUser() {
        User user = new User();
        user.setId(RandomUtil.randomInt(10));
        user.setName(RandomUtil.randomString(3));
        return user;
    }

如何运用到逃逸分析技术

栈上分配

一般来说,JIT即时编译技术中的栈上分配和标量替换基本都是同时出现的,按照上文所述,假如上述代码所返回的user对象仅仅是获取当前用户的年龄,那么我们就可以直接在方法内完成逻辑计算并直接返回,这样对象就没有发生逃逸,如此对象便可直接在栈帧上进行分配,有效减小JVM垃圾回收的压力。

scss 复制代码
 Map<Integer, User> userMap = new HashMap<>();


    public int getUserAgeById(int id) {
       User user = new User();
        user.setId(RandomUtil.randomInt(10));
        user.setName(RandomUtil.randomString(3));
        //打印用户信息
        printUserInfo(user);
    }

分离对象或标量替换

如果仅仅是操作未逃逸对象的某些简单运算,我们同样可以只在栈帧内使用这个对象,如此JVM就会将这个对象打散,将对象打散为无数个小的局部变量,实现标量替换,如下所示,这段代码没有发生逃逸,则JVM会避免创建Point

arduino 复制代码
public static void main(String args[]) {
    alloc();
}
class Point {
    private int x;
    private int y;
}
private static void alloc() {
    Point point = new Point(1,2);
    System.out.println("point.x" + point.x + ";point.y" + point.y);
}

进而直接标量替换,直接在栈上分配x和y的值,完成输出打印。

csharp 复制代码
private static void alloc() {
    int x = 1;
    int y = 2;
    System.out.println("point.x = " + x + "; point.y=" + y);
}

同步锁消除

这一点就比较有趣了,我们都知道使用StringBuffer可以保证线程安全,因为其操作函数都有带synchronized关键字,那么请问这段代码会上锁吗?

java 复制代码
public void appendStr(int count) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < count; i++) {
            sb.append("no: " + i + " ");
        }
    }

答案是不会,因为我们当前操作的StringBuffer 对象并没有发生逃逸,它仅仅是根据外部传入的count完成拼接并打印结果而已,于是JIT就会进行锁消除的优化操作。如下字节码所示,优化后的StringBuffer被替换为StringBuilder

小结

合理的在栈帧上解决问题可以避免对象逃逸,从而让JIT 尽可能的去进行优化,这一点我想应该是一个Java程序员对于代码的极致追求了。

我是sharkchiliCSDN Java 领域博客专家开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili ,同时我的公众号也有我精心整理的并发编程JVMMySQL数据库个人专栏导航。

参考资料

逃逸分析,yyds!!:mdnice.com/writing/0e0...

本文使用 markdown.com.cn 排版

相关推荐
小菜鸡ps几秒前
纯个人大白话--flowable多实例加签与减签
后端·工作流引擎
+VX:Fegn08951 分钟前
计算机毕业设计|基于springboot + vue作业管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
王中阳Go12 分钟前
告别调包侠!2026年Go/Java程序员的AI架构师实战转型指南
后端·go
⑩-14 分钟前
SpringCloud-Feign&RestTemplate
后端·spring·spring cloud
我是谁的程序员16 分钟前
抓包工具有哪些?代理抓包、数据流抓包、拦截转发工具
后端
开心猴爷1 小时前
APP 上架苹果 App Store 被拒,并不总是产品问题
后端
Li_7695321 小时前
Redis —— (五)
java·redis·后端·spring
用户47949283569151 小时前
你每天都在用的 JSON.stringify ,V8 给它开了“加速通道”
前端·chrome·后端
JIngJaneIL1 小时前
基于java+ vue办公管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
uzong1 小时前
如何将项目做出 owner 的感觉
后端