内联缓存是JVM解释执行invokevirtual时自动启用的优化机制,通过缓存“对象类型→方法入口”映射实现单态调用近乎静态绑定,多态或超多态则降级查表,final/private/static方法不参与。

内联缓存(Inline Cache,IC)不是你写代码时能调用的API,而是JVM在解释执行invokevirtual指令时自动启用的底层优化机制。它不改变程序行为,但能把原本每次都要查虚方法表(vtable)的开销,压缩到接近直接调用的水平——前提是调用点“够稳定”。
单态内联缓存让虚调用快得像静态调用
当某个invokevirtual位置(比如animal.speak()这行字节码)在运行中反复遇到同一类实例(如总是Dog),JVM会在解释器里生成一段可修改的机器码桩(ICStub),直接记住“Dog.class → Dog.speak”这个映射。下次再调,跳过类型判断和vtable查找,一拍即合。
- 单态(monomorphic)是IC最高效形态,虚方法调用开销≈普通方法调用
- 这种优化只对解释执行阶段生效;一旦方法被JIT编译,IC就退场,由C1/C2的去虚拟化接管
- final、private、static方法不走
invokevirtual,自然也不参与IC
多态与超多态会显著拖慢性能
如果一个调用点频繁切换接收者类型(比如List list在循环里一会儿是ArrayList,一会儿是LinkedList),IC就会从单态降级为多态(poly-morphic),甚至超多态(megamorphic)。这时JVM要么缓存多个类型→方法映射(查表变长),要么干脆放弃缓存,每次都回退到完整vtable查找。
- 超多态(megamorphic)是性能红灯信号:日志里出现该词,说明该调用点已失去预测性
- 接口方法(
invokeinterface)也有IC,但因需查itable且类型更难收敛,命中率通常更低 - IC退化常触发去优化(deoptimization),导致代码临时回退到解释执行,影响吞吐
怎么让JVM更容易建立高效内联缓存
你不能手动开关IC,但可以引导JVM做出更好推测:
立即学习“Java免费学习笔记(深入)”;
- 避免在热点路径上让同一变量反复指向不同子类实例(例如不要在for循环里交替赋值
list = new ArrayList()和list = new LinkedList()) - 对确定封闭的类型体系,使用
sealed class或final class,减少JVM需要考虑的子类数量 - 确保足够预热:IC需要多次调用才能固化,单次JUnit测试几乎看不到效果;建议循环调用≥1000次且类型不变
- 启用分层编译(默认开启)和类型推测:
-XX:+UseTypeSpeculation有助于JIT后续做更激进的去虚拟化
如何确认内联缓存是否起效
靠看日志,不是靠猜:
- 加参数启动:
-XX:+PrintInterpreter -XX:+TraceClassLoading - 观察日志中是否出现
IC hit(成功命中)或持续刷IC miss(始终无法固化) -
-XX:+PrintInlining显示的是JIT方法内联,和IC无关;但它常与单态IC共存,可辅助判断整体优化程度 - 注意:
javap -c完全看不到IC——它只存在于解释器生成的机器码中,不在字节码层面



















