逃逸分析

作者: gzss | 来源:发表于2019-09-17 21:54 被阅读0次

    逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术,他与类型继承关系分析一样,并不是直接优化代码的手段,而是为其他优化手段提供依据的分析技术。

    逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义之后,他可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

    如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化,比如:

    1、栈上分配(Stack Allocation):Java虚拟机中,在Java堆上分配创建对象的内存空间几乎是Java程序员都清楚的常识了,Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问堆中存储的对象数据。虚拟机的垃圾收集系统可以回收堆中不再使用的对象,但回收动作无论是筛选可回收对象,还是回收和整理内存都需要耗费时间。如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集系统的压力将会小很多。

    2、同步消除(Synchronization Elimination):线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以消除掉。

    3、标量替换(Scalar Replacement):标量(Scalar)是指一个数据已经无法在分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int long 等数值类型以及reference类型等)都不能在进一步分解,他们就可以成为标量。相对的如果一个数据可以继续分解,那他就称作聚合量(Aggregate),Java中的对象就是最典型的聚合量。如果把一个Java对象拆散,根据程序访问情况,将其使用到的成员变量回复原始类型来访问就叫做标量替换。如果你逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建他的若干个被这个方法使用到的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,有很大的概率会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创造条件。

    关于逃逸分析在JDK1.6版本实现,而直到现在这项优化技术也并未成熟,仍有很大的改进余地。不成熟的原因主要是不能保证逃逸分析的性能收益必定高于他的消耗。如果要完全准确的判断一个对象是否会逃逸,需要进行数据流敏感的一系列复杂分析,从而确定程序各个分支执行时对此对象的影响。这是一个相对高耗时的过程,如果分析完后发现没有几个不逃逸的对象,那这些运行期耗时用的时间就白白浪费了,所以目前虚拟机只能采用不那么准确,但时间压力相对较小的算法来完成逃逸分析。还有一点是,基于逃逸分析的一些优化手段,如上面提到的栈上分配,由于HotSpot虚拟机目前的实现方式导致栈上分配实现起来比较复杂,因此在hoTSpot中暂时还没有做这项优化。

    在测试结果中,实施逃逸分析后的程序在MicroBenchmarks中往往能运行出不错的成绩,但是在实际的应用程序,尤其是大型程序中范围发现实施逃逸分析可能出现效果不稳定的情况,或因分析过程耗时但却无法有效判断出非逃逸对象而导致性能(即时编译的收益)有所下降,所以在很长一段时间里,即使是Server Compiler,也默认不开启逃逸分析,甚至某些版本中还曾经短暂的完全禁止了这项优化。

    如果有需要,并确认对程序运行有益,用户可以使用参数-XX:+DoEscapeAnalysis来手动开启逃逸分析,开启之后可以通过参数-XX:+PrintEscapeAnalysis来查看分析结果。有了逃逸分析支持之后,用户可以使用参数-XX:+EliminateAllocations来开启标量替换,使用-XX:+EliminateLocks来开启同步消除,使用参数-XX:+PrintEliminateAllocations查看标量替换情况。

    相关文章

      网友评论

        本文标题:逃逸分析

        本文链接:https://www.haomeiwen.com/subject/cgncuctx.html