聊点硬核的逃逸分析技术

写在文章开头

你好,我叫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 排版

相关推荐
cnsxjean2 小时前
SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传
java·前端·spring boot·分布式·后端·中间件·架构
kingbal2 小时前
SpringCloud:Injection of resource dependencies failed
后端·spring·spring cloud
刘天远3 小时前
django实现paypal订阅记录
后端·python·django
ℳ₯㎕ddzོꦿ࿐3 小时前
Spring Boot集成MyBatis-Plus:自定义拦截器实现动态表名切换
spring boot·后端·mybatis
逸风尊者3 小时前
开发也能看懂的大模型:RNN
java·后端·算法
小钟不想敲代码4 小时前
第4章 Spring Boot自动配置
java·spring boot·后端
hummhumm5 小时前
第33章 - Go语言 云原生开发
java·开发语言·后端·python·sql·云原生·golang
AskHarries5 小时前
利用 OSHI获取机器的硬件信息
java·后端
凡人的AI工具箱6 小时前
40分钟学 Go 语言高并发:【实战】并发安全的配置管理器(功能扩展)
开发语言·后端·安全·架构·golang
我的运维人生6 小时前
Spring Boot应用开发实战:构建RESTful API服务
spring boot·后端·restful·运维开发·技术共享