jmh 无法直接测量 cpu 缓存命中率,因其仅关注逻辑层性能指标,不暴露硬件缓存层级、不可访问 pmc 寄存器,且受 jvm 内存模型与优化干扰;但可通过顺序/随机遍历等对照实验间接验证局部性效果。
直接用 jmh 测不出 cpu 缓存命中率。jmh 是测 java 方法执行性能的工具,它不暴露硬件缓存层级(l1/l2/l3)、不提供 cache miss/hit 计数寄存器访问能力,也无法绕过 jvm 的内存模型抽象去观测真实缓存行为。
为什么 JMH 本身不能测缓存命中率
JMH 关注的是逻辑层性能:吞吐量、平均延迟、分布分位数。它屏蔽了底层细节,比如:
- JVM 可能将热点代码编译为优化后的机器码,数据被调度进寄存器或预取进缓存,但你无法从 JMH 结果反推 hit/miss 次数;
- Java 数组访问带边界检查,会干扰局部性表现,且对象头、对齐填充等让内存布局不完全可控;
- JMH 运行在受控 JVM 环境中,没有权限读取 CPU 的 PMC(Performance Monitoring Counter)寄存器,如
LLC_MISSES或CACHE_REFERENCES。
但可以用数组 + JMH 间接验证局部性优化效果
虽然不能得到具体百分比,但你可以设计两组对照实验,用 JMH 测出性能差异,从而判断局部性是否起作用:
- 构造一个大数组(例如 10MB),确保远超 L3 缓存,避免全驻留;
- 第一组:顺序遍历(强空间局部性)——
for (int i = 0; i ; - 第二组:跨步遍历(弱局部性)——
for (int i = 0; i (步长 ≈ cache line 大小); - 用 JMH 的
@BenchmarkMode(Mode.AverageTime)对比两者耗时;典型结果是顺序版快 3–5 倍,这就是空间局部性生效的实证。
真要获取命中率数值,得结合系统级工具
若目标是拿到具体缓存命中率数字(如 92.7%),必须跳出 Java 层:
- Linux 下用
perf stat -e cycles,instructions,cache-references,cache-misses运行一个 native 程序(如 C 写的数组扫描); - 命中率 =
1 − cache-misses / cache-references; - Java 程序可借助
Async-Profiler(支持 perf_events)采集硬件事件,但它需启用-XX:+UnlockDiagnosticVMOptions -XX:+EnableJVMCI并依赖 Linux kernel 支持; - 更轻量的方式:用 JMH 配合
@Fork(jvmArgsAppend = "-XX:+PrintGCDetails")观察 GC 行为变化——频繁 minor GC 有时暗示对象分配导致 cache line 颠簸,属于间接线索。
实用建议:把数组作为局部性探针
在 Java 性能调优中,数组是最干净的“局部性载体”:
- 用
int[]而非Integer[],避免指针跳转破坏空间局部性; - 初始化后用
Arrays.fill()预热,让数据进入缓存; - JMH 中加
@Setup(Level.Iteration)确保每次 benchmark 迭代都使用新数组,排除跨迭代缓存污染; - 对比不同大小数组(如 4KB vs 4MB)的遍历耗时,能观察到缓存容量拐点——这是定位 L1/L2 容量的朴素方法。











