逃逸分析及优化

写在最前,本篇文章大部分来源于b站尚硅谷JVM全套教程的提炼,并附带自己的理解。主要是为了帮助自己理解,和用于复习。如果同时还能对其他人有所裨益,那就更好不过了。如果有谬误的地方,还请不吝指出。

逃逸分析

逃逸分析是通过分析对象的动态作用域,当一个对象在方法内被定义后,它可能被外部方法引用,比如:作为调用参数传递,称为方法逃逸;如果被外部线程访问,则称为线程逃逸。

不同的逃逸程度,也对应着不同的优化方法。
逃逸分析默认开启,可以通过指令-XX:-DoEscapeAnalysis关闭(改成+号就为开启)

栈上分配

如果对象不逃逸出线程,就可能在栈上分配。
对象所占内存随栈帧出栈而销毁。即当方法结束,对象就自动销毁了。垃圾回收的压力会大大减小。

同步省略

如果一个对象只能被一个线程访问,那么对这个对象的操作不考虑同步。即可以安全地消除同步措施。
即使显示地注明synchronized关键字,编译器也会将其消除掉。

注意,字节码文件还是会有同步的指令,只是在运行的时候被消除了。

标量替换

有的对象不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存(堆),而是存在CPU寄存器(栈)中。

标量指无法再分解为更小数据的数据,基本数据类型就是标量。其它可以分解的则称为聚合量

java对象为聚合量,因为其可以分解为其它的聚合量和标量。

如果一个对象能证明不会被方法以外的代码访问,并且这个对象可以被拆分,那么程序执行时,不会创建这个对象,而是用若干个成员变量(分解为原始类型)来代替。这是栈上分配的特殊情况。

指令:XX:-EliminateAllocations

此处感觉尚硅谷讲的还有些模糊,于是翻出了深入理解Java虚拟机,下面展示一下优化过程。

public int test(int x){
     int xx = x + 2;
     Point p = new Point(xx, 42);
     return p.getX();
 }

将Point类的构造函数和getX()方法进行内联优化:

public int test(int x){
     int xx = x + 2;
     Point p = point_memory_alloc();
     p.x=xx;
     p.y=42;
     return p.x;
 }

经过逃逸分析,发现test()中的Point对象实例不会逃逸,这样就可以进行标量替换,将内部的x和y置换,分解为局部变量,从而避免Point对象实例被创建:

public int test(int x){
     int xx = x + 2;
     int px=xx;
     int py=42;
     return px;
 }

通过数据流分析,发现py的值对方法不会造成影响,于是将无效代码消除,得到最终优化结果:

public int test(int x){
     return x+2;
 }

在实际应用程序中,实施逃逸分析可能出现效果不稳定的情况,或者分析耗时但却无法有效判别而导致性能下降。

直到JDK7才成为服务端编译器默认开启的选项。

注意,HotSpot虚拟机中没有提供栈上分配的优化。也就是说,我们所见到的优化基本都由标量替换所提供。

关于这一点我其实还是有一点疑惑,于是我查找到了一个可以说是比较准确的答案:When can Hotspot allocate objects on the stack?(需要翻墙)

总结一下:

  1. 栈分配是有条件的:

Hotspot can stack-allocate an object instance:

  1. if all its uses are inlined
  2. it is never assigned to any static or object fields, only to local variables
  3. at each point in the program, which local variables contain references to the object must be JIT-time determinable, and not depend on any unpredictable conditional control flow.
  4. If the object is an array, its size must be known at JIT time and indexing into it must use JIT-time constants.
  1. HotSpot实际上并没有使用栈上分配,其实是用标量替换来达到了栈上分配的效果

与书中的结论是一致的。
注意:虽然可以说标量替换是栈上分配的一个特例,但这两者的实际用法还是有区别的。

标量替换仅在不需要创建指向栈上分配对象的指针时才有效。 例如 C++ 或 Go 中的某些形式的栈上分配能够在栈上分配完整的对象,然后将指向它们的引用或指针传递给被调用的函数。

而且标量替换也要比栈上分配的使用条件更为严格。

因此,如果需要将对象引用传递给非内联方法,即使该引用不会逃逸出被调用的方法,Hotspot 也会始终在堆上分配这样的对象。

笔者还不太理解这篇文章,有余力的同学可以前往这篇文章再看看。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值