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

20 | 方法内联(上)

当前IR图的大小
目标方法的调用次数及大小
程序路径的热度
方法调用的层数
强制不内联
强制内联
利用虚拟机参数-XX:+PrintInlining来打印编译过程中的内联情况
内联规则
生成的机器码越长,越容易填满Code Cache,导致即时编译被关闭
内联越多,编译时间越长,程序达到峰值性能的时刻将被推迟
内联越多,生成代码的执行效率越高
内联目标方法的IR图到调用者方法的IR图中
生成IR图
解析字节码
触发更多的优化
消除调用本身带来的性能开销
在编译过程中,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段
实践
条件
过程
优势
定义
方法内联

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

在前面的篇章中,我多次提到了方法内联这项技术。它指的是:在编译过程中遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段。
方法内联不仅可以消除调用本身带来的性能开销,还可以进一步触发更多的优化。因此,它可以算是编译优化里最为重要的一环。
以 getter/setter 为例,如果没有方法内联,在调用 getter/setter 时,程序需要保存当前方法的执行位置,创建并压入用于 getter/setter 的栈帧、访问字段、弹出栈帧,最后再恢复当前方法的执行。而当内联了对 getter/setter 的方法调用后,上述操作仅剩字段访问。
在 C2 中,方法内联是在解析字节码的过程中完成的。每当碰到方法调用字节码时,C2 将决定是否需要内联该方法调用。如果需要内联,则开始解析目标方法的字节码。
复习一下:即时编译器首先解析字节码,并生成 IR 图,然后在该 IR 图上进行优化。优化是由一个个独立的优化阶段(optimization phase)串联起来的。每个优化阶段都会对 IR 图进行转换。最后即时编译器根据 IR 图的节点以及调度顺序生成机器码。
同 C2 一样,Graal 也会在解析字节码的过程中进行方法调用的内联。此外,Graal 还拥有一个独立的优化阶段,来寻找指代方法调用的 IR 节点,并将之替换为目标方法的 IR 图。这个过程相对来说比较形象一些,因此,今天我就利用它来给你讲解一下方法内联。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

方法内联是编译优化中的重要技术,它可以消除方法调用的性能开销并触发更多优化。在编译过程中,即时编译器会将目标方法的方法体纳入编译范围,并取代原方法调用的优化手段。内联过程包括替换调用者方法的IR图中的调用节点为被调用方法的IR图,替换传入参数节点和数据依赖,以及连接异常处理器。内联触发更多优化,但也会增加编译时间和生成的机器码长度,可能导致Code Cache溢出。因此,即时编译器有一些内联规则,如强制内联、不内联的指令,以及根据方法调用的层数、热度、调用次数和IR图大小来决定内联。总体而言,内联算法更青睐于小方法。读者可以通过虚拟机参数-XX:+PrintInlining来了解编译过程中的内联情况。

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

全部留言(24)

  • 最新
  • 精选
  • Jerry银银
    将Java程序编译字节码的时候,Java编译器会有方法内联的优化吗?

    作者回复: javac几乎不做任何优化

    2019-12-29
    13
  • 饭粒
    这个和 C++ 内联函数类似吧,目的是减少函数调用的开销。最终都是编译器来优化,C++ 通过 inline 声明函数,建议编译器内联编译。Java 是 JVM 自动处理,也可通过 VM 参数控制。

    作者回复: 对的

    2019-12-24
    2
    7
  • 刘冠利
    请问final的使用对内联有多大帮助?

    作者回复: 在(下)篇有介绍

    2018-09-06
    6
  • Geek_987169
    老师,能否提供一个学习IR图的地址?

    作者回复: 这方面的知识网上并不多。可以知乎上搜Sea of nodes IR,看R大的回答,有不少链接可以参考。

    2018-10-21
    4
  • 李二木
    请问方法内联是发生在解释执行阶段吗?这里方法调用可以理解为解释执行中的小部分解释吗?有些困惑,麻烦老师解释执行下。

    作者回复: 方法内联只发生在即时编译器中。 方法调用就是字面意思。在即时编译过程中,即时编译器会将当前方法所包含的方法调用的目标方法纳入编译范围中。

    2018-09-07
    4
  • 皮卡皮卡丘
    这个是方法内联信息吗,怎么和代码里的信息有差别?@ 1 java.lang.Object::<init> (1 bytes) @ 5 java.lang.AbstractStringBuilder::appendNull (56 bytes) callee is too large @ 10 java.lang.String::length (6 bytes) @ 21 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) @ 17 java.lang.AbstractStringBuilder::newCapacity (39 bytes) callee is too large @ 20 java.util.Arrays::copyOf (19 bytes) @ 11 java.lang.Math::min (11 bytes) @ 14 java.lang.System::arraycopy (0 bytes) intrinsic @ 35 java.lang.String::getChars (62 bytes) callee is too large @ 1 java.lang.Object::<init> (1 bytes) @ 13 java/lang/StringIndexOutOfBoundsException::<init> (not loaded) not inlineable @ 30 java/lang/StringIndexOutOfBoundsException::<init> (not loaded) not inlineable @ 65 java/lang/StringIndexOutOfBoundsException::<init> (not loaded) not inlineable @ 75 java.util.Arrays::copyOfRange (63 bytes) callee is too large @ 17 java.lang.AbstractStringBuilder::newCapacity (39 bytes) callee is too large @ 20 java.util.Arrays::copyOf (19 bytes) @ 11 java.lang.Math::min (11 bytes) @ 14 java.lang.System::arraycopy (0 bytes) intrinsic @ 66 java.lang.String::indexOfSupplementary (71 bytes) callee is too large @ 3 java.lang.String::indexOf (70 bytes) callee is too large @ 1 java.lang.Character::toUpperCase (9 bytes)

    作者回复: PrintInlining将打印JVM里所有即时编译的内联优化信息,所以看起来比较杂

    2018-09-05
    2
    3
  • Scott
    最后引入常量后foo方法两个图是一样的,后面一幅图应该if节点被优化掉了吧,直接返回0了。

    作者回复: 多谢指出!

    2018-09-05
  • 木心
    IR只有我看不懂吗?各颜色的模块代表什么意思,还有不同钥匙的线代表什么意思?
    2018-09-11
    5
    56
  • 方法内联,一种优化代码的手段,其目的就是想让代码执行的更快一些,它怎么做到的呢?以前记录过性能优化的思路就那么几种,让赶的快的干,如果实现不了就让干的快的多干,干的慢点少干。方法内联是采用少干活的方式来提高效率的,直接将对应方法的字节码内联过来,省下了记录切换上下文环境的时间和空间。
    2018-09-10
    17
  • 西门吹牛
    内联是一种编译器的优化手段,目的就是让代码执行更快,把频繁调用的方法,进行内联后,把调用的目标方法直接编译成机器代码,减少目标方法频繁调用的开销,如果不内联,程序需要保存当前调用者方法的执行位置,同时还要创建用于调用目标方法的栈帧,目标方法执行结束,还是再恢复调用者方法的执行,开销很大。内联的实现过程有俩种: 第一,在即时编译的过程中,可以根据一定的规则,将目标方法的方法体直接编译为机器码; 第二,在IR 图中替换目标方法调动IR 节点,Java字节码本身作为一种 IR,不可直接优化,所以即时编译器会将字节码转换成可优化的IR,IR可以理解为一种字节码指令在虚拟机中运行的分支流程和数据流程图,IR 图中的每个节点可以看出是程序执行的一个或多个指令,把调用目标方法的IR 节点,替换成目标方法的IR 图,其实就是把俩个方法的IR 图合并,这样可以对合并后的 IR 图进行优化; 无论是哪种内联过程,本质是将俩个方法合并,也就是把目标方法合并到调用方法里面,合并方法之后,还需要将目标方法的参数和返回值,都映射到调用方的方法里面。
    2020-07-20
    1
    5
收起评论
显示
设置
留言
24
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部