专栏首页无题逃逸分析原理

逃逸分析原理

逃逸分析

在编程语言的编译优化原理中,分析指针动态范围的方法称之为逃逸分析。通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸。

常见的逃逸场景:全局变量赋值、方法返回值、实例引用传递

123456789101112131415161718192021

public class A {public static B b; //给全局变量赋值,发生逃逸 public void globalVariablePointerEscape(){ b = new B(); }//方法返回值,发生逃逸 public B methodPointerEscape(){ return new B(); }//实例引用传递,发生逃逸 public void instancePassPointerEscape(){ methodPointerEscape().printClassName(this); }}public class B {public void printClassName(A a){ System.out.println(a.getClass().getName()); }}

逃逸分析原理

我们知道Java对象是在堆里分配的,在调用栈中,只保存了对象的指针。当对象不再使用后,需要依靠GC来遍历引用树并回收内存,如果对象数量较多,将给GC带来较大压力。因此,减少临时对象在堆内存分配的数量是最有效的优化方法。
场景应用一:栈上分配

其实,在java应用里普遍存在一种场景。一般是在方法体内,声明了一个局部变量,且该变量在方法执行生命周期内未发生逃逸(在方法体内,未将引用暴露给外面)。按照JVM内存分配机制,首先会在堆里创建变量类的实例,然后将返回的对象指针压入调用栈,继续执行。这是优化前,JVM的处理方式。 • 逃逸分析优化 - 栈上分配 分析找到未逃逸的变量,将变量类的实例化内存直接在栈里分配(无需进入堆),分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。对比可以看出,主要区别在栈空间直接作为临时对象的存储介质。从而减少了临时对象在堆内的分配数量。 故名思议就是在栈上分配对象,其实目前Hotspot并没有实现真正意义上的栈上分配,实际上是标量替换。

应用场景二:同步消除
在即使编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作。

也许你会觉得奇怪,既然有些对象不可能被多线程访问,那为什么要加锁呢?写代码时直接不加锁不就好了。但是有时,这些锁并不是程序员所写的,有的是JDK实现中就有锁的,比如Vector和StringBuffer这样的类,它们中的很多方法都是有锁的。当我们在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。

12345678910111213141516

public class BufferTest {public static void main(String[] args){ long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i ++){ createStringBuffer("JVM", "EscapeAnalysis"); } long end = System.currentTimeMillis(); System.out.println("it takes " + (end - start) + " ms");}public static String createStringBuffer(String s1, String s2){ StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb.toString(); }}

优化前: it takes 867 ms 优化后: it takes 802 ms 基于逃逸分析,JVM可以判断,如果这个局部变量StringBuffer并没有逃出它的作用域,那么可以确定这个StringBuffer并不会被多线程所访问,那么就可以把这些多余的锁给去掉来提高性能。

应用场景三:标量替换
Java虚拟机中的原始数据类型(int,long等数值类型以及reference类型等)都不能再进一步分解,它们就可以称为标量。相对的,如果一个数据可以继续分解,那它称为聚合量,Java中最典型的聚合量是对象。如果逃逸分析证明一个对象不会被外部访问,并且这个对象是可分解的,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。拆散后的变量便可以被单独分析与优化,可以各自分别在栈帧或寄存器上分配空间,原本的对象就无需整体分配空间了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JAVA多线程构件(java.util.concurrent包下高级工具)

    Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Jav...

    于霆霖
  • 栈上分配和TLAB

    在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程...

    于霆霖
  • AQS源码解析

    * AQS原理 AQS就是一个同步器,要做的事情就相当于一个锁,所以就会有两个动作:一个是获取,一个是释放。获取释放的时候该有一个东西来记住他是被用还是没被用...

    于霆霖
  • Android项目开发全程(三)-- 项目的前期搭建、网络请求封装是怎样实现的

      在前两篇博文中已经做了铺垫,下面咱们就可以用前面介绍过的内容开始做一个小项目了(项目中会用到Afinal框架,不会用Afinal的童鞋可以先看一下上一篇博文...

    codingblock
  • .NET Core微服务之基于MassTransit实现数据最终一致性(Part 2)

      在上一篇中,我们了解了MassTransit这个开源组件的基本用法,这一篇我们结合一个小案例来了解在ASP.NET Core中如何借助MassTransit...

    Edison Zhou
  • 【JDK1.8】JDK1.8集合源码阅读——Set汇总

    joemsu
  • 让IoC动态解析自定义配置(提供基于Unity的实现)

    在《通过自定义配置实现插件式设计》中,通过在运行时对配置的动态解析实现了真正的“插件式”设计,其本质就是让配置自行提供对配置类型实例的创建。在这篇文章中,我们将...

    蒋金楠
  • 【JDK1.8】JDK1.8集合源码阅读——Set汇总

    joemsu
  • java建造者模式

      在现实生活中如果我们需要制造一个比较复杂的东西,比如手机,台式电脑,或者汽车等。如果我们要制造一台电脑的话我们会先将电脑所需的各个部件买回来然后在组装起来成...

    用户4919348
  • Javascript面向对象编程(三):非构造函数的继承

    这个系列的第一部分介绍了"封装",第二部分介绍了使用构造函数实现"继承"。 今天是最后一个部分,介绍不使用构造函数实现"继承"。 一、什么是"非构造函数"的继承...

    ruanyf

扫码关注云+社区

领取腾讯云代金券