微基准测试需预热、多轮采样取中位数、防JIT优化、严格环境控制。预热触发JIT编译,多轮测量剔除极值后算单次耗时,用volatile变量防止优化,绑定CPU核并关闭节能模式以减少干扰。

直接用 System.nanoTime() 测一次就下结论,结果基本不可信。真正可靠的微基准测试,核心不是“怎么记时间”,而是“怎么排除干扰、让时间反映真实逻辑开销”。
预热:让JIT编译器准备好再开始计时
Java 方法首次执行时,会经历解释执行 → C1编译 → C2优化的过程。没预热就测,前几次耗时可能高出几倍甚至几十倍,把编译开销全算进去了。
- 对简单方法(如加法、位运算),空跑或调用 1 万~10 万次即可完成预热
- 对含对象创建、反射、泛型擦除的方法,建议预热 50 万次以上,并观察耗时曲线是否收敛
- 预热阶段不采集数据,也不做统计,只做“触发”作用
多轮采样 + 中位数优先
单次测量受线程调度、TLB缺失、CPU频率跳变等影响极大。哪怕同一段代码,连续测 10 次,结果可能从 8ns 到 200ns 不等。
- 每轮执行 1 万~100 万次目标逻辑,记录该轮总耗时(单位纳秒)
- 重复运行 20~30 轮,得到一组“总耗时”样本
- 剔除首尾各 10% 的极值(例如 30 轮中去掉最慢和最快的 3 个),再取剩余样本的中位数
- 用中位数总耗时 ÷ 单轮执行次数,得到单次耗时(纳秒),再 ÷ 1000 得微秒值
防优化:确保你要测的代码真被运行了
JIT 编译器非常聪明——如果它发现计算结果没被使用,可能直接把整段逻辑删掉,测出来就是 0ns。
- 声明一个
volatile int sink或volatile long sink,把关键计算结果赋给它 - 或者将结果参与后续不可省略的逻辑(如作为 if 条件、数组索引、返回值)
- 避免在待测块内打印日志、创建临时对象、触发 GC(比如字符串拼接、装箱)
环境控制:别让系统拖你后腿
即使代码写得再严谨,OS 层面的干扰也能让结果失真。
- Linux 下用
taskset -c 0 java ...绑定到单个 CPU 核,减少跨核迁移开销 - 关闭 CPU 节能模式:
cpupower frequency-set -g performance - 压测期间关闭 IDE、浏览器、杀毒软件等后台进程
- 避免在测量循环中触发 Young GC;可配合
-Xmx2g -Xms2g -XX:+UseG1GC减少波动


















