逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量引用。正常的方法调用中,方法体中创建的对象将在执行完毕之后,将回收其中创建的对象;故由于无法回收,即成为逃逸。
java Hotspot 编译器能够分析出一个新的对象的引用的使用范围,从而觉得是否需要将这个对象分配到堆上。
/**
* 无逃逸
*/
void test01() {
String test1 = "test1";
}
String test2;
/**
* 逃逸
*/
void test02() {
test2 = "test2";
}
在方法内的变量不会逃逸,在方法外声明的对象会发生逃逸,脱离了方法的控制,方法结束时变量没有结束
方法逃逸:
线程逃逸:
全局逃逸:
一个对象的引用掏出了方法或者线程。例如:一个对象的引用赋值给一个类变量,或者这个对象的用引用作为方法的返回这返回给了调用方法。
参数级逃逸:
方法调用过程中,传递对象给另一个方法
没有逃逸:
一个可以进行标量替换的对象,可以将这个对象不分配在传统的堆上
-XX:+DoEscapeAnalysis //使用 (JDK8中,逃逸分析默认开启。)
-XX:-DoEscapeAnalysis //不用
-XX:+PrintEscapeAnalysis //结果展示
首先需要弄清楚两个概念,静态编译和动态编译
静态编译指的是javac之类的编译,动态编译指的是JIT等即时编译。
因为Sun公司不在对javac这方面的进行优化。所以优化基本上都是在JVM中,
通过查询资料得到
Excelsior JET 这个东西要比我们主流的HOTSPOT更早实现了逃逸分析和相关优化,
但是不知道Java在运行期间不知道会 加载上什么代码。Java有很多的小方法,虚方法的调用,难以进行静态分析。
同时逃逸分析需要 在比较大的代码快中工作才会比较有效。因为编译器需要了解更多的代码。用于更加准确的判断对象有没有逃逸,
只在小块代码上分析的话,很多的时候都得到错误的判断,就没有效果了。
结论:
逃逸分析可以在编译期运行,但是由于技术上面的一些原因,所以是在运行期间进行的比较多。
对数据流敏感的一系列的分析,从而确定能够程序各个分支执行对这个对象的影响,耗时较长
栈上分配:
普通对象在堆中分配,各线程共享。但有GC消耗。
当确定对象不会发生方法逃逸时,可在线程栈上分配对象。此时对象生命周期和方法相同,随栈帧出栈时即可销毁,不需要GC了。
同步消除:
线程同步降低了并发性和程序性能。
锁消除:当确定对象不会发生线程逃逸时,可消除该对象不必要的同步操作(永不会竞争)。具体来说,JVM在编译器运行时会扫描代码,当检查到那些不可能存在共享区竞争,但却有互斥同步的代码,直接将这样的多此一举的同步消除
锁粗化:JVM针对那些反复在一段代码中对同一对象加锁的情况,将同步锁放在最外层包住这里面的多次同步锁,同时取消内部的同步锁
标量替换:
标量是指一个数据无法分解成更小的数据,基础类型的数据都算是标量,对象可以被分解成其他的成员变量,所以说变量不是
标量,那么标量替换是指在运行期间,不必要创建对象,而是改成创建对象的成员变量,不仅可以让对象成员在栈上分配和读写之外,还
可以为后一步的优化技术创造了条件。
无法保证逃逸分析的收益大于进行此操作带来的性能消耗。所以JVM目前采用不准确、时间较短算法进行逃逸分析,以权衡收益和开销。
如果大家对java架构相关感兴趣,可以关注下面公众号,会持续更新java基础面试题, netty, spring boot,spring cloud等系列文章,一系列干货随时送达, 超神之路从此展开, BTAJ不再是梦想!