• JVM 逃逸分析 同步缺省 标量替换 栈上分配


    简介

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

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

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

    逃逸分析

    逃逸分析(Escape Analysis)简单来讲就是,分析Java对象的动态作用域,JVM可以分析新创建对象的使用范围,并决定是否在堆上分配内存。

    当一个对象被定义之后,可能会被外部对象引用,称之为「方法逃逸」;也有可能被其他线程所引用,称之为「线程逃逸」。

    逃逸分析的 JVM 参数如下:

    • 开启逃逸分析:-XX:+DoEscapeAnalysis
    • 关闭逃逸分析:-XX:-DoEscapeAnalysis
    • 显示分析结果:-XX:+PrintEscapeAnalysis

    逃逸分析技术在 Java SE 6u23+ 开始支持,并默认设置为启用状态,可以不用额外加这个参数。

    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 对象就没有「逃逸」。

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

    同步缺省(锁消除)

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

    标量替换

    「标量(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。

    栈上分配

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

    逃逸分析并不成熟

    关于逃逸分析的论文在1999年就已经发表了,但直到JDK 1.6才有实现,而且这项技术到如今也并不是十分成熟的。

    其根本原因就是无法保证逃逸分析的性能消耗一定能高于他的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。

    一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是不逃逸的。那这个逃逸分析的过程就白白浪费掉了。

    虽然这项技术并不十分成熟,但是他也是即时编译器优化技术中一个十分重要的手段。

    参考:

    深入理解Java中的逃逸分析

    Java中的逃逸分析

  • 相关阅读:
    随手vue笔记 (三)
    vue中如何使用定义好的变量设置css样式
    .net6 WebApi 之 Configuration
    .net6 webApi IoC SqlSugar的日常使用
    file Download 监听文件下载完成状态
    sqlserver数据库回滚
    git常用命令
    2018年以来互联网医院相关核心政策文件达23份
    Java 基础面试题20211228
    git add A 可以免于 git rm
  • 原文地址:https://www.cnblogs.com/hongdada/p/14517125.html
Copyright © 2020-2023  润新知