1. 简介
在本篇文章中,我们将深入探讨 Java 虚拟机(JVM)中的方法内联(Method Inlining)是什么,它是如何工作的,以及我们如何获取和解读 JVM 中与内联相关的信息。
同时还会介绍如何利用这些信息来优化我们的代码。
2. 方法内联是什么?
简单来说,方法内联是一种运行时优化技术,它通过将频繁调用的方法调用替换为方法体本身,从而减少方法调用的开销。
⚠️ 注意:这个优化并不是由传统的 javac
编译器完成的,而是由 JVM 内部的 即时编译器(Just-In-Time Compiler,JIT) 完成的。javac
只负责将 Java 源码编译为字节码,而 JIT 才是真正做“魔法优化”的部分。
✅ 一个重要的好处是:即使你使用老版本的 Java 编译了代码,只要在新版本的 JVM 上运行,性能仍然会更好 —— 你不需要重新编译源码,只需要升级 JVM 即可。
3. JIT 是如何做内联的?
JIT 的主要目标是通过内联频繁调用的方法来减少方法调用的开销。在决定是否对某个方法进行内联时,JIT 会考虑两个关键因素:
✅ 1. 方法调用频率(热点方法)
JIT 会使用计数器记录每个方法的调用次数。当某个方法被调用超过一定阈值(默认是 10,000 次)时,它就会被标记为“热点方法”。
可以通过以下 JVM 参数调整这个阈值:
-XX:CompileThreshold=10000
✅ 2. 方法体大小
即使方法是“热点”,如果它太大,JIT 也不会内联它。默认最大内联大小由以下参数控制:
-XX:FreqInlineSize=325
这个值在 64 位 Linux 上默认是 325 字节(bytecode instructions)。不建议随意更改这个值,除非你非常清楚其影响。
❌ 注意事项
- JIT 通常会内联
static
、private
和final
方法。 - 对于
public
方法,只有在 JVM 能确定它是唯一实现时才会被内联。 - 如果有子类重写了该方法,内联就不会发生,性能反而下降。
4. 如何查看哪些方法被内联了?
我们当然不想靠猜来判断 JIT 做了什么。幸运的是,JVM 提供了一些参数,可以帮助我们打印出内联日志:
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
-XX:+PrintCompilation
:打印 JIT 编译发生的时间和方法。-XX:+PrintInlining
:打印哪些方法被内联了,以及为什么没有被内联。
输出日志会以树状结构显示方法调用关系,叶子节点会标注如下信息:
inline (hot)
:热点方法,已内联 ✅too big
:非热点,但方法太大,未内联 ❌hot method too big
:热点方法,但太大,未内联 ❌
⚠️ 重点关注 hot method too big
的方法,这些是优化的优先目标。
✅ 优化建议
如果你发现某个热点方法中包含复杂的 if
、switch
或循环逻辑,建议你将这些逻辑拆分成更小的方法,提高粒度,方便 JIT 优化。
4.1. 实战示例
下面是一个简单的例子,用于演示方法内联的过程。
📌 示例代码 1:计算前 N 个正整数的和
public class ConsecutiveNumbersSum {
private long totalSum;
private int totalNumbers;
public ConsecutiveNumbersSum(int totalNumbers) {
this.totalNumbers = totalNumbers;
}
public long getTotalSum() {
totalSum = 0;
for (int i = 0; i < totalNumbers; i++) {
totalSum += i;
}
return totalSum;
}
}
📌 示例代码 2:调用方法
private static long calculateSum(int n) {
return new ConsecutiveNumbersSum(n).getTotalSum();
}
📌 示例代码 3:多次调用
for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) {
calculateSum(i);
}
🔍 情况一:调用次数 < 10,000(未达到热点阈值)
# 无输出
🔍 情况二:调用次数 > 10,000(达到热点)
664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
@ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) inline (hot)
✅ 方法被成功内联。
🔍 情况三:强制限制方法大小(方法太大)
-XX:FreqInlineSize=10
此时方法大小为 12 字节,大于限制,输出如下:
330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
@ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) hot method too big
❌ 方法未被内联。
⚠️ 再次强调:除非绝对必要,否则不要修改 -XX:FreqInlineSize
的默认值。
5. 总结
本文介绍了 JVM 中的方法内联机制,以及 JIT 编译器如何决定是否对某个方法进行内联。我们还展示了如何通过 JVM 参数查看内联信息,并提供了优化“热点但太大”方法的建议。
✅ 关键点回顾:
- 方法内联由 JIT 完成,不是
javac
。 - 热点方法 + 方法体大小合适 = 内联 ✅
- 避免手动内联,JIT 更高效。
- 使用
-XX:+PrintInlining
查看内联日志。
📌 所有代码示例均可在 GitHub 仓库 中找到。