编译与优化


1. 逃逸分析

逃逸是指一个对象在某个方法中被创建后,会被其他方法或其他线程所引用。

  • 方法逃逸:该对象被作为参数传递到其他方法中。
  • 线程逃逸:该对象被关联到其他线程中可以访问到的实例变量中。

线程逃逸的逃逸程度比方法逃逸更高。

如果能证明一个对象不会发生逃逸,或者逃逸程度较低,则可以对该实例对象进行不同的优化。JDK 1.7 默认开启逃逸分析:

-XX:+DoEscapeAnalysis // 打开逃逸分析
-XX:+PrintEscapeAnalysis // 查看逃逸分析结果
-XX:+EliminateAllocations // 开启标量替换

1.1 栈上分配

一般来说,对象实例会在堆中进行内存分配,但对堆的内存回收和整理会耗费大量资源。如果确定了一个对象不会发生逃逸或只可能发生方法逃逸,则可以把对象分配在栈上,使变量随着方法的结束而自动销毁,从而减轻垃圾回收系统的压力。

  • 只支持方法逃逸,不支持线程逃逸。
  • 栈上分配没有被直接实现,而是采用了标量替换来代替。因为目前对一个对象进行彻底的逃逸分析所占用的计算资源太大。

1.2 标量替换

若一个数据无法分解被更小的数据,则可以被称为标量。如 Java 中的 int,reference 类型等。否则,被称为聚合量,如对象。

标量替换是指如果逃逸分析能证明一个对象不会逃逸出该方法,并且该对象可以被拆散,则程序执行时可能并不会去创建这个对象,而是直接创建它的成员变量。这样,标量成员可以直接分配在栈上。

  • 标量替换是栈上分配的一种特例,它的要求比栈上分配更苛刻。它要求对象不能发生方法逃逸和线程逃逸。

举例,我们要创建一个 User 对象,但只在一个线程中调用了 getUser() 方法访问了 nameage 属性,则会进行标量替换,不会创建对象 User,只会创建它的两个成员变量。

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;
    }
}

标量替换后的等价代码:

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

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

1.3 同步消除

如果发现某个变量不会被多线程访问,即一定是线程安全的,则会执行锁消除


文章作者: Yu Yang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yu Yang !
 上一篇
ConcurrentHashMap 源码分析 ConcurrentHashMap 源码分析
ConcurrentHashMap0. 为什么 HashMap 线程不安全 多线程插入数据导致数据被覆盖 在 JDK 1.7 时,采用的头插法,多线程同时执行 resize() 可能造成环形链表,造成死循环 JDK 1.8 中代替为尾插法,
2021-02-21
下一篇 
3.线程状态及其转化 3.线程状态及其转化
3. 线程状态及其转化线程是轻量级进程,所以线程和进程状态一致。 Java 线程状态转化图: 3.1 Java 线程的 6 个状态Thread.State 源码: public enum State { NEW,
  目录