逃逸分析
堆是分配对象存储的唯一选择吗?
- 在深入了解java虚拟机中关于Java堆内存有这样一段描述:随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配,标量替换优化技术将会导致一些微妙变化,所有的对象都分配到堆上也渐渐变得不那么"绝对"了
- 在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普通的常识,但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配,这样就无需再堆上分配内存,也无须进行垃圾回收了,这也是最常见的堆外存储技术
- 此外,基于OpenJDK深度定制的TaoBaoVM,其中创新的GCIH(GC Invisible heap)技术实现off-heap,将生命周期较长的Java对象从heap中移至heap外,并且GC不能管理GCIH内部的对象,以此达到降低GC的回收频率和提升GC的回收效率的目的
逃逸分析
- 如何将堆上的对象分配到栈,需要使用逃逸分析手段
- 这是一种可用有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法
- 通过逃逸分析,Java HotSpot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上
- 逃逸分析的基本行为是分析对象动态作用域
- 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸
- 当一个对象在方法被定义后,它被外部方法所引用,则认为发生逃逸,例如作为参数传递到其他方法中,或者作为返回值返回.
- 在JDK 6u23版本之后,HotSpot中默认已经开启了逃逸分析
基于逃逸分析进行代码优化
-
栈上分配:JIT编译器在编译期间根据逃逸分析结果,如果一个对象没有发生逃逸,那么就可能优化成栈上分配
-
同步省略:如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步
- 在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步代码块所使用的锁对象是否只能被一个线程访问而没有发布到其他线程,如果没有,那么JIT编译器在编译这个 同步块的时候就会取消对部分代码的同步,这样就能大大提高并发和性能,这个取消同步的过程就叫同步省略,也叫锁消除
-
分离对象或标量替换,有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在cpu的寄存器中
- 标量(Scalar)是指一个无法再分解成更小更小数据的数据,Java中的原始数据类型就是标量
- 相对的,那些可以分解的数据叫做聚合量(Aggregate),java中的对象就是聚合量,因为它可以分解成其他聚合量和标量
- 在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替,这个过程就是标量替换
逃逸分析并不成熟
- 关于逃逸分析的论文在1999年就已经发布了,但直到JDK1.6才实现,而且这项技术到如今也并不是十分成熟的
- 其根本原因就是无法保证逃逸分析的性能消耗一定低于垃圾回收的消耗,虽然经过逃逸分析可以做标量替换,栈上分配和锁消除,但是逃逸分析自身也是需要进行一些列复杂的分析的,这其实也是一个相对耗时的过程
- 一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是不逃逸的,那这个逃逸分析的过程就白白浪费掉了
- 虽然这项技术并不十分成熟,但是它也是即时编译器优化中一个十分重要的手段
- JVM会在栈上分配那些不会逃逸的对象,这在理论上是可行的,但是取决于JVM设计者的选择,HotSpot中其实并未这么做,所以可以明确所有的对象实例都是创建在堆上的