1, 是JVM优化技术,它不是直接优化手段,而是为其它优化手段提供依据。
逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。Java在Java SE 6u23以及以后的版本中支持并默认开启了逃逸分析的选项。Java的 HotSpot JIT编译器,能够在方法重载或者动态加载代码的时候对代码进行逃逸分析,同时Java对象在堆上分配和内置线程的特点使得逃逸分析成Java的重要功能。
2,逃逸分析主要就是分析对象的动态作用域。
基于逃逸分析,一个对象会可能会被用三种逃逸状态标记:
- 全局级别逃逸:一个对象可能从一个方法或者当前线程中逃逸。再明确一点,如果一个对象被作为一个方法的返回值,那么对象被标记为全局逃逸状态。如果一个对象作为类静态字段(static field)或者类字段(field),同样会被标记为全局逃逸状态。另外,如果我们复写了一个方法的finalize()方法,那么这个类的对象都会被标记为全局逃逸状态并且一定会放在堆内存中,这也符合情理,因为这些对象需要对于JVM的finalizer必须是可见的(所以发生逃逸了)。当然,还有其他的一些情况也会让对象标记为全局逃逸状态。
- 参数级别逃逸:如果一个对象被作为参数传递给一个方法,但是在这个方法之外无法访问或者对其他线程不可见,这个对象标记为参数级别逃逸。
- 无逃逸状态:一个对象不会产生逃逸
标记为全局级别逃逸或者参数级别逃逸的对象必须在堆中分配空间,但是参数级别逃逸是可能在内存中去掉对象同步锁的,因为上面已经解释,参数级别逃逸对象不会被其他线程访问。
无逃逸状态的对象的内存分配会更加自由,可能会在栈上分配,也可能会在堆上分配。事实上,在某些情况下,甚至根本不会去创建一个对象,而直接使用该对象的标量值代替,比如仅仅在栈上创建一个int,去代替一个Integer对象。因为只有一个线程可以访问该对象,所以对象上的同步锁自然会被去掉。例如,我们使用无逃逸状态的StringBuffer(较之StringBuilder,StringBuffer是线程安全的,所有方法都是synchronized),那么这种情况下,所有方法的同步锁都会被去掉,提高执行效率。
3,逃逸有两种:方法逃逸和线程逃逸。
方法逃逸(对象逃出当前方法):
当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其它方法中。
线程逃逸((对象逃出当前线程):
这个对象甚至可能被其它线程访问到,例如赋值给类变量或可以在其它线程中访问的实例变量
4,如果不存在逃逸,则可以对这个变量进行优化
(1)栈上分配。
在一般应用中,不会逃逸的局部对象占比很大,如果使用栈上分配,那大量对象会随着方法结束而自动销毁,垃圾回收系统压力就小很多。
(2)同步消除
线程同步本身比较耗时,如果确定一个变量不会逃逸出线程,无法被其它线程访问到,那这个变量的读写就不会存在竞争,对这个变量的同步措施可以清除。
(3)标量替换。
1)标量就是不可分割的量,java中基本数据类型,reference类型都是标量。相对的一个数据可以继续分解,它就是聚合量(aggregate)。
2)如果把一个对象拆散,将其成员变量恢复到基本类型来访问就叫做标量替换。
3)如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那么程序真正执行的时候将可能不创建这个对象,而改为直接在栈上创建若干个成员变量。
5,逃逸分析还不成熟。
(1)不能保证逃逸分析的性能收益必定高于它的消耗。
判断一个对象是否逃逸耗时长,如果分析完发现没有几个不逃逸的对象,那时间就白白浪费了。
(2)基于逃逸分析的优化手段不成熟,如上面提到的栈上分配,由于hotspot目前的实现方式导致栈上分配实现起来复杂。
6,相关JVM参数
-XX:+DoEscapeAnalysis 开启逃逸分析
-XX:+PrintEscapeAnalysis 开启逃逸分析后,可通过此参数查看分析结果。
-XX:+EliminateAllocations 开启标量替换
-XX:+EliminateLocks 开启同步消除
-XX:+PrintEliminateAllocations 开启标量替换后,查看标量替换情况。
TLAB
JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。