• 首页

  • 归档

  • 清单
    歌单 分类 标签

  • 心情

  • 画廊

  • 关于

  • 友链

  • 留言板
H i , X i a o y a o
H i , X i a o y a o

逍遥叹

那一年,我也变成了光!!

03
05
JVM性能调优-理论篇

JVM性能调优-理论篇:04-方法内联讲解

发表于 2021-03-05 • jvm • 被 45 人看爆

上一节我们讲过JVM会用编译的形式为代码提速,在这个基础上,即时编译器还做了很多的优化措施,从而进一步的提升性能;这一节探讨的方法内联就是即时编译器的优化措施之一;

来看一下下面的代码

public class InlineTest1 {
    private static int add1(int x1, int x2, int x3, int x4){
        return add2(x1, x2) + add2(x3, x4);
    }

    private static int add2(int x1, int x2){
        return x1 + x2;
    }
}

add1方法里面调用了两次add2方法,大家想编译器会怎么优化这段代码呢;
我们知道,调用方法需要经过压栈和出栈的操作,进入方法的时候会向栈里面压入一个元素,return的时候就向栈里面弹出这个元素;
而压栈和出栈操作都是有开销的,压栈的时候会往栈里面存入数据,存在内存的开销,同时压栈和出栈也是都需要时间的,所以还有时间的开销;
如果上述代码调用的次数不多,那么压栈和出栈的开销就无所谓了,但是假设这段代码调用的非常频繁,比方说每秒要调用2万次,累计下来的开销还是比较可观的;
那么是不是有什么办法去优化呢?
JVM会自定识别热点方法,并自动的去进行方法内联;

编译器优化-方法内联

  • 把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用

上述代码经过内联后的代码如下:

# 内联后
private static int addInline(int x1, int x2, int x3, int x4){
    return x1 + x2 + x3 + x4;
}

JVM会自动把add1方法和add2方法合并到一起,从而减少压栈和出栈的操作

使用方法内联的条件

  • 方法体足够小:如果方法体太大,JVM是不会内联的

    • 热点方法:如果方法体小于325字节会尝试内联,可用-XX:FreqInlineSize修改大小
      有关JVM如何寻找热点方法,上一节已经探讨过了
    • 非热点方法:如果方法体小于35字节,会尝试内联,可用-XX:MaxInlineSize修改大小
  • 被调用方法运行时的实现可以被确定唯一

    • static方法、private方法及final方法,JIT可以唯一确定具体的实现代码
    • public的实例方法,指向的实现可能是自身、父类、子类的代码(因为有多态的存在),当且仅当JIT能够唯一确定方法的具体实现时,才有可能完成内联

根据上述两点内联条件,我们可以做出如下总结

方法内联注意点

  • 尽量让方法体小一些
    在写代码的时候,应当尽量避免在一个方法里面编写大量的代码,让方法体尽量小一点
  • 尽量使用final、private、static关键字修饰方法,避免因为多态,而对方法做额外的类型检查
    很可能在检查之后,方法还没有可能内联,因为没有办法唯一确定方法的实现
  • 一些场景下,可通过修改JVM的参数,去减少热点的阈值,或者修改方法体的阈值,从而让更多方法内联
    比如说:-XX:FreqInlineSize 和 -XX:MaxInlineSize

内联可能带来的问题

  • CodeCache的溢出,导致JVM退化成解释执行模式
    首先要强调的是,方法内联不是万能药,它也是有缺点的;
    内联本质上是一种用空间换时间的玩法,也就是即时编译器在编译期间,把方法调用连接起来,从而减少进栈和出栈的开销,但是经过内联之后的代码会变多,而增加的代码量又取决于方法的调用次数以及方法本身的大小;
    所以,在一些极端场景下,内联可能会导致 CodeCache 的溢出,CodeCache是热点代码的一个缓存区,即时编译器编译后的代码和本地方法代码都会存在CodeCache里面,这块空间是比较有限的,JDK8默认情况下只有240M,这块空间一旦溢出的话,甚至可能会导致JVM放弃编译运行,而退化成解释执行模式;

内联相关JVM参数

参数名默认说明
-XX:+Printlnlining-打印内联详情,该参数需和-XX:+UnlockDiagnosticVMOptions配合使用
-XX:+UnlockDiagnosticVMOptions-打印JVM诊断相关的信息
-XX:MaxlnlineSize=n35如果非热点方法的字节码超过该值,则无法内联,单位:字节
-XX:FreqlnlineSize=n325如果热点方法的字节码超过该值,则无法内联,单位:字节
-XX:lnlineSmallCode=n1000目标编译后生成的机器码开销大于该值,则无法内联,单位:字节
-XX:MaxlnlineLevel=n9内联方法的最大调用帧数(嵌套调用的最大内联深度)
-XX:MaxTrivialSize=n6如果方法的字节码少于该值,则直接内联,单位:字节
-XX:MinlnliningThreshold=n250如果目标方法的调用次数低于该值,则不内联
-XX:LiveNodeCountlnliningCutoff=n40000编译过程中最大活动节点数(IR节点)的上限,仅对C2编译器有效
-XX:lnlineFrequencyCount=n100如果方法的调用点(call site)的执行次数超过该值,则触发内联
-XX:MaxRecursivelnlineLevel=n1递归调用大于这么多次就不内联
-XX:+lnlineSynchronizedMethods开启是否允许内联同步方法
分享到:
JVM性能调优-理论篇:05-逃逸分析、标量替换、栈上分配
读写分离、分库分表-01:如何选择垂直切分、水平切分
  • 文章目录
  • 站点概览
逍遥叹

小小程序员-逍遥叹

那一年,我也变成了光!!

Github QQ Email RSS
看爆 Top5
  • 为什么要写这篇博客? 414次看爆
  • 开发手记:Centos环境常用命令 401次看爆
  • JVM内置故障排查工具:jhsdb 396次看爆
  • 学习笔记:Java动态代理 343次看爆
  • 学习笔记:Java中的锁 325次看爆

Copyright © 2021 逍遥叹 · 粤ICP备17164804号-1

Proudly published with Halo · Theme by fyang · 站点地图