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

24 | 字段访问相关优化

被标记为volatile的字段
两次存储之间没有读取
思考即时编译器如何优化除法操作
不可达分支消除
死代码消除
优化冗余的字段存储操作
缓存字段存储、读取的值
不可达分支消除
部分死存储消除
死存储消除
冗余存储操作消除条件
消除冗余存储操作
消除冗余的存储节点
优化实例字段和静态字段访问
缓存值失效时采取保守策略
替换字段读取节点为缓存值
缓存字段存储节点的值
替换对象字段访问为局部变量访问
将对象拆散为字段
标量替换
栈上分配
锁消除
实践环节
总结与实践
死代码消除
字段存储优化
字段读取优化
标量替换
逃逸分析
字段访问相关优化

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

在上一篇文章中,我介绍了逃逸分析,也介绍了基于逃逸分析的优化方式锁消除、栈上分配以及标量替换等内容。
其中的标量替换,可以看成将对象本身拆散为一个个字段,并把原本对对象字段的访问,替换为对一个个局部变量的访问。
class Foo {
int a = 0;
}
static int bar(int x) {
Foo foo = new Foo();
foo.a = x;
return foo.a;
}
举个例子,上面这段代码中的bar方法,经过逃逸分析以及标量替换后,其优化结果如下所示。(确切地说,是指所生成的 IR 图与下述代码所生成的 IR 图类似。之后不再重复解释。)
static int bar(int x) {
int a = x;
return a;
}
由于 Sea-of-Nodes IR 的特性,局部变量不复存在,取而代之的是一个个值。在例子对应的 IR 图中,返回节点将直接返回所输入的参数。
经过标量替换的bar方法
下面我列举了bar方法经由 C2 即时编译生成的机器码(这里略去了指令地址的前 48 位)。
# {method} 'bar' '(I)I' in 'FieldAccessTest'
# parm0: rsi = int // 参数x
# [sp+0x20] (sp of caller)
0x06a0: sub rsp,0x18 // 创建方法栈桢
0x06a7: mov QWORD PTR [rsp+0x10],rbp // 无关指令
0x06ac: mov eax,esi // 将参数x存入返回值eax中
0x06ae: add rsp,0x10 // 弹出方法栈桢
0x06b2: pop rbp // 无关指令
0x06b3: mov r10,QWORD PTR [r15+0x70] // 安全点测试
0x06b7: test DWORD PTR [r10],eax // 安全点测试
0x06ba: ret
在 X86_64 的机器码中,每当使用 call 指令进入目标方法的方法体中时,我们需要在栈上为当前方法分配一块内存作为其栈桢。而在退出该方法时,我们需要弹出当前方法所使用的栈桢。
由于寄存器 rsp 维护着当前线程的栈顶指针,因此这些操作都是通过增减寄存器 rsp 来实现的,即上面这段机器码中偏移量为 0x06a0 以及 0x06ae 的指令。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

基于逃逸分析的优化方式是本文的重点,其中包括标量替换和字段访问优化。通过标量替换,对象字段的访问被替换为对局部变量的访问,从而简化代码并提高执行效率。文章还详细介绍了即时编译器对字段访问的优化策略,包括缓存字段存储节点的值、替换字段读取节点、处理volatile字段和锁操作等情况。通过优化实例字段和静态字段的访问,即时编译器能够减少总的内存访问次数,提升程序性能。此外,死代码消除也是关键内容之一,包括局部变量的死存储消除以及不可达分支消除。这些优化方式和消除策略对于理解Java虚拟机的优化技术具有重要参考价值。文章内容深入浅出,为读者提供了深入了解即时编译器优化方式的机会。

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

全部留言(18)

  • 最新
  • 精选
  • Void_seT
    因为x/y会有除0异常,这部分代码是否会被优化掉,不太确定,望老师指点。

    作者回复: 对的,因为有除0异常所以编译器没法优化掉这个除法

    2018-09-14
    17
  • 小文同学
    老师,请问一下,读这个专栏有点像涨视野的感觉,暂时来说对我看代码提供了新的灵感,但目前只能听,没法提问,这种感觉是是因为我底子不够么?还是我实践得比较少?

    作者回复: 按照自己的节奏学习就好啦,等以后遇到问题了也可以回来提问的。

    2018-09-20
    10
  • Scott
    "其中真正的安全点测试是 0x06ba 指令"应该是0x06b7指令

    作者回复: 多谢指出!已修改!

    2018-09-14
    2
    4
  • 永烁星光
    Return x+y ;

    作者回复: 课后实践中的除法无法优化,因为可能存在除零异常。即时编译器需要判断除数是否为0

    2018-09-14
    3
  • Scott
    这一篇洋洋洒洒,其实覆盖了更多后端优化的算法,是否可以罗列一下对应算法名称供参考?

    作者回复: 我记得就叫read elimination和write elimination。第三节的是dead store elimination和partial redundancy elimination。 Graal的相关代码在PEReadEliminationClosure以及ReadEliminationClosure中。可以自行阅读。

    2018-09-14
    2
  • 李二木
    感觉写代码的能力水平跟编译器优化工作量还是有点关系的。

    作者回复: 一般我们写代码都会在保持代码可读性的同时,尽量减少编译器工作量

    2018-09-14
    2
  • 李二木
    除法操作直接消除,返回return x+y; ?

    作者回复: 消除不了,有位同学答出来了,有除零异常

    2018-09-14
    2
  • 誓言的梦
    有除零的异常 是通过什么手段/机制知道 还是写死的除法不能优化 或者回不回是通过 检测当输入除数为0就不优化 输入不为0时才优化
    2018-12-19
    6
  • 剑八
    总结下来: 编绎器对于读取及存储指令的优化 对于读取,如果涉及一个堆对象同一个字段的多次读取,且不涉及该字段的写操作则会优化成方法执行中的缓存。只在第一次读取该对象字段时会从堆中取。会将该对象字段值缓存在局部变量表中。后面读取从这个地方取。这就涉及到一个多线程的可见性问题。如果想要去除可以设置为volatile或加lock,syncronized锁。 对于写操作,如果一个方法体对于一个变量的写是会覆盖的,则只会保留最后一个写操作。 还有一个优化则可以对于不可到达的分支代码进行消除,本质上都是减少不执行的代码,减少编绎成机器码后存储到code cache中的大小。
    2020-06-14
    4
  • test
    判断输入如果y为0则抛出异常,否则直接返回x+y
    2020-02-23
    2
    3
收起评论
显示
设置
留言
18
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部