JIT内联需同时满足体积小、无多态风险、调用频繁三条件,否则宁可不优化;确认是否内联应重点分析-XX:+PrintInlining中“too big”或“not inlineable (virtual)”等拒绝原因,而非仅看是否标记inline (hot)。

高频调用的业务方法,只要满足体积小、无多态风险、调用频繁三个条件,JIT 就大概率内联;否则它宁可不优化,也不会冒险——这不是配置能强行绕过的逻辑。
怎么确认你的方法是否被内联了
光看 -XX:+PrintInlining 输出里有没有 inline (hot) 是误导性的。真正关键的是查「为什么没 inline」:
- 如果日志里反复出现
too big,说明方法字节码超出了默认阈值-XX:MaxInlineSize=35,不是“没触发”,而是“被拒了” - 看到
not inlineable (virtual)或needs method data,基本等于告诉你是虚方法且 JIT 还没完成类层次分析(CHA),得再跑一会儿或加-XX:+UnlockDiagnosticVMOptions才能看到完整原因 - 配合
-XX:+PrintCompilation看对应方法是否已进 C2 编译队列——没编译就不可能内联
哪些方法天然容易被内联
不是“写得快”就容易内联,是 JVM 在运行时能安全、低成本地做决定才行:
-
private static final方法:无多态风险,JVM 启动就能确定不可重写,优先级最高 - 实例方法加
final:比 public 实例方法强得多,但前提是该类没被动态代理或子类继承覆盖(比如 Spring AOP 代理后,原方法就变虚了) - 像
String.length()这种单字段返回的方法:字节码只有 3–4 行,return value.length,JIT 几乎必内联 - 避免在高频路径上用
public实例方法封装简单逻辑,比如getAmount()返回一个字段——不如直接访问字段或改用final方法
调大 -XX:MaxInlineSize 有用吗
有用,但非常有限,且容易适得其反:
- 把
-XX:MaxInlineSize=100当成万能解药,结果发现内联后生成的机器码暴涨,L1 指令缓存(通常 32–64 KB)频繁 miss,整体反而更慢 -
-XX:FreqInlineSize=325是给“已被识别为热点”的方法准备的,但它只对 C2 编译生效,且仍受 CHA 分析结果约束——不是设了就一定 inline - 真正该做的,是把业务逻辑拆开:把计算密集部分抽成
private static小方法,把 IO/锁/分支判断等“污染项”移出高频路径
内联不是越深越好
JIT 默认内联深度是 9 层(-XX:MaxInlineLevel=9),但业务代码里常见的“方法 A → B → C → D”链式调用,一旦某环是虚方法或体积超标,整条链就断在那儿:
- 别指望靠加参数让 JVM 把 5 层嵌套的
public方法全内联——它宁愿保持调用,也不愿承担多态误判风险 - 用
-XX:+PrintAssembly(需 hsdis)看汇编输出,如果发现循环体里还有一堆call指令,说明内联根本没发生,别浪费时间调参数 - 最有效的干预,是把核心计算逻辑收束到一个
private static方法里,让它成为整个调用链的“锚点”
内联效果高度依赖运行时实测,不是静态代码审查能预判的;最容易被忽略的一点是:哪怕方法完全符合所有条件,JIT 也必须先完成类加载、方法解析、计数器累积、CHA 分析这一整套流程——冷启动后前几万次调用,永远在解释执行。


















