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 通常会内联 staticprivatefinal 方法。
  • 对于 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 的方法,这些是优化的优先目标。

✅ 优化建议

如果你发现某个热点方法中包含复杂的 ifswitch 或循环逻辑,建议你将这些逻辑拆分成更小的方法,提高粒度,方便 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 仓库 中找到。


原始标题:Method Inlining in the JVM