深入拆解 Java 虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
87446 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
模块四:黑科技 (3讲)
深入拆解 Java 虚拟机
15
15
1.0x
00:00/00:00
登录|注册

23 | 逃逸分析

观察部分逃逸分析的效果
验证ArrayList.iterator中的新建对象是否被逃逸分析优化
判断对象在部分分支中逃逸
与控制流有关
标量替换
栈上分配
锁消除
对象是否作为方法调用的调用者或参数
对象是否存入堆中
判断对象是否逃逸
确定指针动态范围的静态分析
实践
部分逃逸分析
优化
逃逸分析的依据
逃逸分析
逃逸分析

该思维导图由 AI 生成,仅供参考

我们知道,Java 中Iterable对象的 foreach 循环遍历是一个语法糖,Java 编译器会将该语法糖编译为调用Iterable对象的iterator方法,并用所返回的Iterator对象的hasNext以及next方法,来完成遍历。
public void forEach(ArrayList<Object> list, Consumer<Object> f) {
for (Object obj : list) {
f.accept(obj);
}
}
举个例子,上面的 Java 代码将使用 foreach 循环来遍历一个ArrayList对象,其等价的代码如下所示:
public void forEach(ArrayList<Object> list, Consumer<Object> f) {
Iterator<Object> iter = list.iterator();
while (iter.hasNext()) {
Object obj = iter.next();
f.accept(obj);
}
}
这里我也列举了所涉及的ArrayList代码。我们可以看到,ArrayList.iterator方法将创建一个ArrayList$Itr实例。
public class ArrayList ... {
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
...
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
因此,有同学认为我们应当避免在热点代码中使用 foreach 循环,并且直接使用基于ArrayList.size以及ArrayList.get的循环方式(如下所示),以减少对 Java 堆的压力。
public void forEach(ArrayList<Object> list, Consumer<Object> f) {
for (int i = 0; i < list.size(); i++) {
f.accept(list.get(i));
}
}
实际上,Java 虚拟机中的即时编译器可以将ArrayList.iterator方法中的实例创建操作给优化掉。不过,这需要方法内联以及逃逸分析的协作。
在前面几篇中我们已经深入学习了方法内联,今天我便来介绍一下逃逸分析。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

逃逸分析是Java虚拟机中的一种静态分析技术,用于确定对象的指针动态范围。通过分析对象是否逃逸,即时编译器可以进行优化,如锁消除、栈上分配和标量替换。逃逸分析判断对象是否逃逸的依据包括对象是否被存入堆中以及是否被传入未知代码中。基于逃逸分析的优化可以减轻对Java堆的压力,提高程序性能。部分逃逸分析引入了与控制流有关的逃逸分析,能够优化更多情况,不过编译时间更长。总结与实践中介绍了逃逸分析的判断依据和优化方式,并提供了验证逃逸分析优化效果的代码示例。逃逸分析技术在Java虚拟机中发挥着重要作用,为程序性能优化提供了有力支持。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入拆解 Java 虚拟机》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(22)

  • 最新
  • 精选
  • 魏春河
    怎么看出来对象是否放入堆中?不是所有的对象都在堆中吗
    2018-09-12
    10
    23
  • Darren
    逃逸分析的主要优化点是:栈上分配,标量替换,同步消除。其中同步消除比较少,栈上分配在HotSpot中暂未实现,主要是标量替换。 逃逸分析的缺点是:分析过程比较耗费性能或者分析完毕后发现非逃逸的对象很少。 逃逸程度:不逃逸,方法逃逸,线程逃逸;其中栈上分配不支持线程逃逸,标量替换不支持方法逃逸。
    2020-03-06
    1
    12
  • 房艳
    看完老师的讲解,我又看了下面的这个文章,感觉好像更理解了一些。 逃逸分析 逃逸分析并不是直接的优化手段,而是一个代码分析,通过动态分析对象的作用域,为其它优化手段如栈上分配、标量替换和同步消除等提供依据,发生逃逸行为的情况有两种:方法逃逸和线程逃逸。 1、方法逃逸:当一个对象在方法中定义之后,作为参数传递到其它方法中; 2、线程逃逸:如类变量或实例变量,可能被其它线程访问到; 如果不存在逃逸行为,则可以对该对象进行如下优化:同步消除、标量替换和栈上分配。 同步消除 线程同步本身比较耗,如果确定一个对象不会逃逸出线程,无法被其它线程访问到,那该对象的读写就不会存在竞争,则可以消除对该对象的同步锁,通过-XX:+EliminateLocks可以开启同步消除。 标量替换 1、标量是指不可分割的量,如java中基本数据类型和reference类型,相对的一个数据可以继续分解,称为聚合量; 2、如果把一个对象拆散,将其成员变量恢复到基本类型来访问就叫做标量替换; 3、如果逃逸分析发现一个对象不会被外部访问,并且该对象可以被拆散,那么经过优化之后,并不直接生成该对象,而是在栈上创建若干个成员变量; 通过-XX:+EliminateAllocations可以开启标量替换, -XX:+PrintEliminateAllocations查看标量替换情况。 栈上分配 故名思议就是在栈上分配对象,其实目前Hotspot并没有实现真正意义上的栈上分配,实际上是标量替换。 ...... 链接:https://www.jianshu.com/p/20bd2e9b1f03
    2021-01-21
    5
  • 乘风
    看了此篇后有一些疑惑: 1.为什么对象存入到堆中就无法追踪其代码位置? 当基于全局的优化确定对象的作用域限定在方法内部,其引用不会发生逃逸,这样的对象虽然存在堆中但其引用作用域固定,不会发生方法逃逸。 2.逃逸分析的判断依据是对象是否存入到堆中,而后文又讲到HotSpot并没有采用栈上分配,那不是意味着对象是一定分配在堆中吗?
    2019-07-18
    1
    4
  • 李二木
    本章介绍逃逸分析的优化作用,那么它有什么不足的地方吗?
    2018-09-12
    1
    2
  • Scott
    你好,我翻了一下R大关于escape analysis的一篇知乎回答,里面提到C2可以对不逸出当前线程的锁做消除,这个过程是怎样的?
    2018-09-12
    1
    2
  • xzy
    最后的结果还是:所有对象都在堆里面
    2020-11-12
    1
  • GaGi
    对于如何判断对象是逃逸的,我的理解是这样: 1、对象如果在堆中,其他线程是可以获取到这个对象的引用,这时如果很多地方引用到这个对象,那么就会导致即时编译器无法追踪所有使用该对象的代码位置; 2、关于第二点,文中说的比较清晰,就是对象如果是作为调用者调用一个未知方法/作为参数传入未知方法,这时就可以认为是逃逸的; 对于上面这两点,应该是连带的;也就是说先满足对象是在堆中存储,并且对象有涉及到未知代码中就认为是逃逸的;不知道理解正不正确,如果理解不正确,麻烦老师纠正下
    2020-04-17
    1
  • 9700
    为啥hashCode方法不能内联,22节介绍的native方法,只要被标注了intrinsic,都会被直接内联的啊。
    2019-06-28
    3
    1
  • 倔强
    老师讲的非常好,对jvm的了解更加深入了一些
    2018-09-12
    1
收起评论
显示
设置
留言
22
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部