https://docs.oracle.com/javase/8/docs/technotes/guides/vm/performance-enhancements-7.html#escapeAnalysis
https://blog.51cto.com/14510351/2441245
https://segmentfault.com/a/1190000038262877#
https://www.cnblogs.com/zhuxiaopijingjing/p/12829448.html
逃逸分析:编译器 通过 分析对象的使用范围 来 决定是否在Java堆上分配(官方原话)。
对象的逃逸状态:
1、全局逃逸(GlobalEscape)
一个对象的作用范围逃出了当前方法或者当前线程,例如:
- 存储在静态字段中的对象
- 存储在已逃逸对象的字段中
- 作为当前方法结果返回的对象
2、参数逃逸(ArgEscape)
一个对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸,这个状态是通过被调方法的字节码确定的。3、没有逃逸
即方法中的对象没有发生逃逸。
全局逃逸
public class EscapeTest {
public static Object globalVariableObject;
public Object instanceObject;
//对象是一个静态变量
public void globalVariableEscape(){
globalVariableObject = new Object(); //方法中创建的对象 赋值给 静态变量(类变量) ,外部线程可见,发生逃逸
}
//对象作为当前方法的返回值
public Object returnObjectEscape(){
return new Object(); //对象被传进了不确定的代码中去运行,发生逃逸
}
public void noEscape(){
Object noEscape = new Object(); //仅创建线程可见,对象无逃逸
}
}
参数逃逸
https://blog.51cto.com/14510351/2441245
public class EscapeTest {
//EscapeTest对象 本身可以作为参数传递
public void todo(EscapeTest escapeTest){
...
}
public static void main(String[] args) {
EscapeTest e1 = new EscapeTest();
EscapeTest e2 = new EscapeTest();
//e1.todo(e1) 或 e1.todo(e2)等;
}
}
e1、e2都会参数逃逸;
虽然看起来e1不会逃逸到main方法之外,但是实际上e1可以作为todo(EscapeTest escapeTest)方法的参数,故e1逃逸到了mian方法之外;
逃逸分析的作用
逃逸分析的作用,就是筛选出没有发生逃逸的对象,从而可以进行以下三方面的优化:
https://my.oschina.net/leandison/blog/1846557
栈上分配
Java中对象是分配在堆上的,但是当能够明确对象不会发生逃逸时,就可以对这个对象做一个优化,不将其分配到堆上,而是直接分配到栈上,这样在方法结束时,这个对象就会随着方法的出栈而销毁,这样就可以将少垃圾回收的压力。
同步消除(锁消除)
因为同步锁是非常消耗性能的,所以当编译器确定一个对象没有发生逃逸时,它便会移除该对象的同步锁。
这就是 所谓的 锁消除;
标量替换
标量指的是没有办法再分解为更小的数据的类型,即Java中的基本类型,我们平时定义的类都属于聚合量。
标量替换即是将一个聚合量拆成多个标量来替换,即用一些基本类型来代替一个对象。如果明确对象不会发生逃逸,并且可以进行标量替换的话,那么就可以不创建这个对象,而是直接使用基本类型来代替,这样也就可以节省创建和销毁对象的开销。
其它知识点
https://www.cnblogs.com/zhangjianbing/p/13702803.html
Java的逃逸分析只能发生在即时编译(JIT)期,为什么不能在静态编译(javac)中?
总结来说是可以发生在静态编译期的,但是Java的分离编译和动态加载使得前期的静态编译的逃逸分析比较困难或收益较少,所以目前Java的逃逸分析只发在JIT的即时编译中,因为收集到足够的运行数据JVM可以更好的判断对象是否发生了逃逸。