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

17 | 即时编译(下)

触发更多的优化
节省编译时间和内存空间
避免编译不会用到的代码
剪掉从未执行过的分支
类型profile
分支profile
循环回边的执行次数
方法的调用次数
使用参数 -XX:CompileCommand='print,*ClassName.methodName' 打印程序运行过程中即时编译器生成的机器码
Java虚拟机采取去优化,退回至解释执行并重新收集相关的profile
基于分支profile和类型profile的优化都作出假设
解释执行过程中仅收集方法的调用次数和循环回边的执行次数
根据去优化的原因决定是否保留机器码和何时重新编译Java方法
复杂的去优化过程
插入陷阱进行去优化
从执行即时编译生成的机器码切换回解释执行
根据数据流分析,优化代码
优化条件去虚化内联
根据类型profile假设对象的动态类型
基于分支profile的优化
假设接下来的执行同样会按照所收集的profile进行
C2根据收集的数据进行猜测
在解释执行过程中收集这些profile的情况
分支profile和类型profile的收集会带来性能开销
0层和3层还收集用于4层C2编译的数据
分层编译中的0层、2层和3层进行profiling
总结与实践
去优化
基于类型profile的优化
基于分支profile的优化
Profiling
Java虚拟机中的即时编译

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

今天我们来继续讲解 Java 虚拟机中的即时编译。

Profiling

上篇提到,分层编译中的 0 层、2 层和 3 层都会进行 profiling,收集能够反映程序执行状态的数据。其中,最为基础的便是方法的调用次数以及循环回边的执行次数。它们被用于触发即时编译。
此外,0 层和 3 层还会收集用于 4 层 C2 编译的数据,比如说分支跳转字节码的分支 profile(branch profile),包括跳转次数和不跳转次数,以及非私有实例方法调用指令、强制类型转换 checkcast 指令、类型测试 instanceof 指令,和引用类型的数组存储 aastore 指令的类型 profile(receiver type profile)。
分支 profile 和类型 profile 的收集将给应用程序带来不少的性能开销。据统计,正是因为这部分额外的 profiling,使得 3 层 C1 代码的性能比 2 层 C1 代码的低 30%。
在通常情况下,我们不会在解释执行过程中收集分支 profile 以及类型 profile。只有在方法触发 C1 编译后,Java 虚拟机认为该方法有可能被 C2 编译,方才在该方法的 C1 代码中收集这些 profile。
只要在比较极端的情况下,例如等待 C1 编译的方法数目太多时,Java 虚拟机才会开始在解释执行过程中收集这些 profile。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了Java虚拟机中的即时编译(JIT)技术以及其在profiling和基于分支profile的优化方面的应用。在JIT编译中,0层、2层和3层都会进行profiling,收集方法的调用次数和循环回边的执行次数等数据,用于触发即时编译。文章指出,分支profile和类型profile的收集会带来性能开销,但对于C2编译器进行优化至关重要。基于分支profile的优化可以剪掉从未执行过的分支,节省编译时间和内存空间,并触发更多的优化。此外,即时编译器还会根据分支profile计算程序执行路径的概率,以便优先处理概率较高的路径。文章还介绍了基于类型profile的优化和去优化的过程。总的来说,本文深入解析了Java虚拟机的profiling以及基于所收集的数据的优化和去优化过程,对于提高Java应用程序的性能具有重要意义。

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

全部留言(16)

  • 最新
  • 精选
  • 这节是即时编译器的有关优化、去优化、以及何时优化和为什么去优化的内容。可能比较底层,看不见摸不着,一句两句也说不清楚,所以,有点晦涩。 不过经过反复看有点感觉了,小结如下: 1:profile-是收集运行时状态信息,用于编译器优化,当然,收集信息也是耗性能的,所以,也是有前提条件的,当存在优化的可能性时才去费劲吧啦的收集相关信息 2:本节介绍的两种优化的方式思路,都是采用取巧少做事情的方式实现,是建立在假设有些事情不需要做的前提下采用的优化措施 3:如果假设失败,那就去优化呗!还用原来的方式老老实实的解释执行就完了 上述思路是理解了,不过具体实现还是蒙蒙的,应该是水平有限理解不到位吧!整体还是有收获的,所以,非常感谢!

    作者回复: 我觉得总结得很到位了。 这篇文章主要就是在介绍profile-guided optimization。介绍了两个优化的案例,以及过于乐观的优化出错时怎么补救。

    2018-09-02
    32
  • xiaobang
    想问下生成的机器码如何和其它未编译的字节码交互?比如相互调用,访问对象内的字段,new对象和对应的gc

    作者回复: 在同一方法内,所有的字节码都被编译了。机器码与字节码的切换在方法调用(或者循环回边,对应OSR编译)。 对于已经在堆上的对象,字节码和机器码所使用对象布局是一致的,解释执行器从哪个偏移量访问某字段,机器码也会从这个偏移量访问字段。(对于标量替换的新建对象,机器码会在去优化时重建对象。) JVM里的Runtime是共享的,因此像new对象,实际上都会走到同一个底层方法中。GC是独立的组件,跟字节码机器码没关系,所有new对象都会被JVM runtime追踪。

    2018-10-10
    2
    8
  • 徐志毅
    雨迪老师,请问有什么方式可以跟踪JMM里主内存与工作内存的交互,如什么时候加载到工作内存、什么时候同步到主内存~ 盼回复,谢谢

    作者回复: JMM的工作内存是个虚拟概念,映射到我们的体系架构就是CPU缓存。 要追踪缓存更新事件的话,估计得看perf,VTune等支持CPU performance counter的工具啦

    2018-08-29
    2
    5
  • 乔毅
    请教下老师,JIT利用SIMD进行优化的实现程度?实践中看到大多是仅仅做了循环展开。换言之,有没有什么最佳实践,可以写出JIT SIMD优化友好的代码。

    作者回复: 马上会专门开一篇介绍。可以关注一下OpenJDK的Panama项目,会提供vector API

    2018-09-03
    1
  • 李二木
    感觉好难,我想问下在现实情况下通过编译器调优的情况多吗?

    作者回复: 调优的情况不多的,能做的即时编译器都帮你做了

    2018-08-30
    1
  • 茶底
    大佬我已经把graal拿到手了。目前用了下gu下语言挺好使的。但是这个怎么编译啊。。。

    作者回复: Java 10自带Graal编译器。Oracle OTN下的GraalVM也是编译好的版本,无需另外编译。 如果想要改Graal源代码后编译,可以参考github.com/oracle/graal/tree/master/compiler/README.md

    2018-08-30
  • 西门吹牛
    这篇文章讲的分支优化,类型优化,其实就是对应了CPU指令执行时候的分支预测和冒险技术,JVM只是个虚拟机,都是在模拟整个计算机的运行过程,所以抽象出了内存模型等概率,因为java 在解释执行的时候,效率不高,但是解释执行的好处就是应用程序的启动不需要加载很多类,启动快。 为了平衡解释执行的效率问题,引入即时编译技术,即时编译是把一整块代码(可以是一个方法也可以是一个循环块)直接编译成一段机器码,以便后续热点代码重复使用。 CPU在执行指令的时候,从硬件方面考虑,CPU很快,CPU完全不会等某个条件判断结果出来以后在根据判断结果确定执行那条指令,而是根据预测的方法,不等判断结果就根据判断执行后续的指令,如果对了,就省去等待时间,虽然CPU等待一条指令执行完毕的时间很短,但是这个时间跟cpu的频率一比,还是很可观的,如果预测错了,那就把执行错误的指令移除,涉及到加载到寄存器的数据都删掉,然后在按正确的分支去执行。 java在即时编译的过程中,编译出来的是一整段机器码,程序还没运行呢,无法判断走哪个分支,所以是通过收集的profile 数据进行预测,预测对了当然好,错就错了,在回去走一遍就好,但是根据profile 的数据进行预测,错误的概率不高,试想一下,if条件判断,预测错误的概率是百分之五十,通过收集数据的分析,完全可以把这个错误的概率降到很低,所以jvm在即时编译的时候,完全有必要进行预测,虽然收集数据有开销,但是同样带来的性能效率也有提升。 之前只是对cpu的分支预测和数据冒险有了解,今天看了老师的文章,真是大赞。方法论都是通用的,只是在不同的地方用了不同的实现。
    2020-07-15
    24
  • 任鑫
    《老子》尝言:”将欲取之,必先予之“。我们想要使用编译优化来提高代码的执行效率,这就是你想要”取“,然而优化是要付出代价的,这就是”予“;全部都编译优化当然代价太大,我们希望让这个代价小一点,因此需要在前期代码执行过程中收集它的profile,根据这个统计消息来决定哪些优化,怎么优化,这就是要确定优化的策略,这又是一个”取“,收集profile会降低代码运行效率,这就是为了确定这个编译优化策略所要付出的代价,这就是第二个”予“。一切优化均需权衡代价,不管你是”时间换空间“、”空间换时间“什么的,就是一个”将欲取之必先与之“。
    2020-05-27
    2
  • 随心而至
    这两节都是讲JVM想做一些假设,来提高程序的运行效率(优化);如果假设错了就重新来过(去优化,退化为解释执行)。 这里JVM尝试做的事情,感觉和底层CPU想做的分支预测如出一辙。 具体可参考:https://time.geekbang.org/column/article/102166
    2019-10-18
    1
  • 陈吉米
    那么有什么规则,可以让代码尽可能被优化?
    2018-08-29
    1
    1
收起评论
显示
设置
留言
16
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部