聊点硬核的逃逸分析技术

写在文章开头

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

相关推荐
酷爱码1 小时前
springboot 动态配置定时任务
java·spring boot·后端
计算机-秋大田1 小时前
基于SpringBoot的美食烹饪互动平台的设计与实现(源码+SQL脚本+LW+部署讲解等)
vue.js·spring boot·后端·课程设计·美食
加油,旭杏2 小时前
【go语言】grpc 快速入门
开发语言·后端·golang
brzhang2 小时前
墙裂推荐一个在 Apple Silicon 上创建和管理虚拟机的轻量级开源工具:lume
前端·后端
沈韶珺3 小时前
Visual Basic语言的云计算
开发语言·后端·golang
沈韶珺3 小时前
Perl语言的函数实现
开发语言·后端·golang
美味小鱼4 小时前
Rust 所有权特性详解
开发语言·后端·rust
我的K84094 小时前
Spring Boot基本项目结构
java·spring boot·后端
慕璃嫣5 小时前
Haskell语言的多线程编程
开发语言·后端·golang
晴空๓5 小时前
Spring Boot项目如何使用MyBatis实现分页查询
spring boot·后端·mybatis