内联缓存是hotspot虚拟机的隐式优化策略,通过运行时类型反馈对invokevirtual调用点做单态推测:若接收者99%属同一类,则生成带快速类型检查的特化机器码,省去虚表查找;它依赖统计而非静态缓存,与方法内联协同但概念不同。

内联缓存不是 Java 的显式机制,而是 HotSpot 的隐式优化策略
Java 语言规范里没有“内联缓存”这个 API 或关键字,它也不是开发者能配置或观察的运行时结构。真正起作用的是 HotSpot 虚拟机在解释执行阶段对 invokevirtual 和 invokeinterface 调用点(call site)做的类型探测与推测性优化。它的核心目标很直接:避免每次调用都查虚方法表(vtable)或接口方法表(itable),尤其当绝大多数调用都落在同一个具体类上时。
单态场景下,JIT 直接硬编码目标方法入口
当某个 invokevirtual 调用点在运行中被观测到 99% 以上的接收者(receiver)属于同一类(比如总是 ArrayList),HotSpot 就判定为单态(monomorphic)。此时 JIT 编译器会生成高度特化的机器码:
- 插入一条快速类型检查指令(如
cmp比较对象的 klass 指针) - 若匹配,直接跳转到该类对应方法的已编译入口(no vtable lookup)
- 若不匹配,触发去优化(deoptimization),退回到解释器或慢路径处理
这相当于把“查表 + 跳转”压缩成“比一比 + 跳”,省去了虚表索引计算、内存加载等开销。你用 -XX:+PrintAssembly 可以在汇编输出里看到类似 cmp %rax,0x8(%r12) 后紧跟 je 的模式。
单态缓存依赖运行时类型反馈,不是静态记录
HotSpot 不维护传统意义上的“缓存条目数组”,而是靠每个调用点附带的类型反馈(type profile)数据驱动决策。这些数据来自解释器执行时的计数器:
- 每个 invokevirtual 字节码位置会关联一个“热点计数器”和“类型采样槽”
- 首次调用时记录接收者类型;后续调用若类型一致,就累加计数
- JIT 编译时读取该计数,判断是否满足单态阈值(默认约 99%)
这意味着单态优化不是靠“记住上次调用是谁”,而是靠统计意义上足够稳定——所以它对局部性敏感,也容易被突然混入的其他类型打破(比如测试时塞了个 LinkedList 进来)。
它和方法内联(Inlining)是两回事,但常协同工作
注意区分两个概念:
- 内联缓存:解决“调用哪个实现”的问题,发生在动态分派环节,不改变调用结构
- 方法内联:解决“要不要跳进另一个方法”的问题,把被调方法体复制到调用点,彻底消除调用开销
但在单态条件下,JIT 往往先做内联缓存(确保目标明确),再进一步尝试将那个确定的方法体直接内联进来。比如 list.size() 在单态于 ArrayList 时,不仅跳得快,还可能把 ArrayList.size() 的几行字节码直接展开,连跳转都省了。










