一、什么是指针逃逸
逃逸分析(Escape Analysis),在计算机编译器优化原理中,逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。计算机软件方面,逃逸分析指的是计算机语言编译器语言优化管理中,分析指针动态范围的方法。通俗讲,如果一个对象的指针被多个线程或者方法引用,那么称这个指针发生了逃逸。java语言也有逃逸的情况存在,实例代码如下。
package com.lwh.jvm; public class B { public void printClassName(G g){ System.out.println(g.getClass().getName()); } }
package com.lwh.jvm; public class G { public static B b; public void globalVariablePointerEscape(){//给全局变量赋值,发生逃逸 b = new B(); } public B methodPointerEscape(){//方法返回值,发生逃逸 return new B(); } public void instancePassPointerEscape(){ methodPointerEscape().printClassName(this);//实例引用发生逃逸 } }
上述代码,举了3个常见的指针逃逸的场景:全局变量赋值、方法返回值、实例引用传递。
二、逃逸分析对java编译器有什么好处呢?
我们知道java对象是在堆中进行分配的,因此java对象的创建和回收对系统的开销是很大的。java语言被诟病的一个地方,就是认为java性能慢的一个原因就是java不支持运行时栈上分配对象,缺少像c#里面的值对象或者C++里面的struct结构。栈里面只保存了对象的指针,当对象不再被使用后,需要依靠GC来遍历引用树并回收内存。如果对象数量较多,讲给GC带来较大的压力,也间接影响了应用的性能。减少临时对象在堆内存分配的数量,将是一种有效的优化方法。
在java应用里普遍存在一种场景,一般是在方法体内,声明了一局部变量,且该变量在方法执行声明周期内未发生逃逸,因为在方法体内未将引用暴露给外面。按照jvm内存分配机制,首先会在堆里面创建类变量类的实例,然后将返回的对象指针压入调用栈在,继续执行,这是优化前的方式。
我们可以采用 逃逸分析原理对jvm进行优化, 对于没有发生逃逸的对象由于生命周期都在一个方法体内,因此他们可以在运行时栈上进行分配并销毁,提高java的运行效率。
栈上分配:采用逃逸分析技术原理对JVM进行优化,即针对栈的重新分配方式。首先我们先要分析并且找到未逃逸的变量,将变量类的实例化内存直接在栈上进行分配(无需进入堆),分配完成后,继续再调用栈内执行,最后线程结束,栈空间被回收局部变量对象也被回收。通过这种优化方式,与优化前的方案主要区别在栈空间直接作为临时对象的存储介质,从而减少了对象在堆内存的分配数量。
package com.lwh.jvm; /** * 未发生指针逃逸的局部变量可以使用栈上分配对象方式来提高效率。 * 对于没有发生逃逸的对象由于生命周期都在一个方法体内,因此他们可以在运行时栈上进行分配并销毁,提高java的运行效率 * 栈上分配:采用逃逸分析技术原理对JVM进行优化,即针对栈的重新分配方式。首先我们先要分析并且找到未逃逸的变量, * 将变量类的实例化内存直接在栈上进行分配(无需进入堆),分配完成后,继续再调用栈内执行,最后线程结束,栈空间被回收 * 局部变量对象也被回收。通过这种优化方式,与优化前的方案主要区别在栈空间直接作为临时对象的存储介质,从而减少了对象在堆内存的分配数量。 * @author luwenhu * */ public class StackAllocateObject { public void my_method(){ B b=new B(); //user b //.... b=null; } }
(3)逃逸分析还有其他两个优化应用
a、同步消除:我们知道线程同步的代价是相当高的。同步的后果是降低并发性和性能。逃逸分析可以判断出某个对象是否始终会被一个线程访问。如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作,提高并发程序的性能。
b、矢量替换:逃逸分析方法如果发现对象的内存存储结构不需要连续进行的话,就可以将对象的部分甚至全部都保存早CPU寄存器内,这样就大大提高访问速度了。