system.nanotime()仅可用于测量代码段耗时差值,需预热、多轮采样、隔离状态并防止jit优化,否则单次测量无意义。

直接用 System.nanoTime() 测单次执行时间几乎没意义——它本身开销就达 10–50 纳秒,而真实算法耗时可能只有几十纳秒。要可靠对比两个算法,关键不是“怎么测”,而是“怎么控制变量、消除干扰、提取有效信号”。
必须成对调用,只取差值
System.nanoTime() 返回的是一个单调递增的纳秒计数器值,起点未知、不映射真实时间,因此绝对值无业务含义。唯一合法用法是:在待测代码前后各调用一次,相减得到纳秒级耗时差。
- 正确写法:
long start = System.nanoTime(); doWork(); long end = System.nanoTime(); long ns = end - start; - 错误写法:把
nanoTime()和currentTimeMillis()相减、拿单次结果直接比大小、用它生成日志时间戳 - 单位换算:纳秒转微秒除以 1000(
ns / 1000),转毫秒除以 1_000_000;注意整数截断,如需小数精度用ns / 1000.0
预热 + 多轮采样,避开 JIT 干扰
JVM 在首次执行方法时会触发类加载、解释执行、JIT 编译等过程,前几次运行耗时远高于稳定态。若不预热,A 算法测的是“冷启动+编译”,B 算法测的是“已优化”,对比完全失真。
- 先空跑或调用目标方法 10,000 次以上,确保 JIT 已完成热点编译
- 正式测量至少 100 轮,每轮执行 1,000–10,000 次目标逻辑(减少计时器开销占比)
- 每轮记录总纳秒耗时,再除以次数得该轮单次均值;最终取所有轮次均值的中位数,比平均值更能抵抗 GC 或中断毛刺
隔离状态,避免交叉污染
如果 A 和 B 共享同一个 ArrayList、缓存对象或静态字段,B 的测量可能受益于 A 触发的 CPU 缓存预热、TLB 加载甚至对象晋升到老年代,导致 B “假快”。
- 为每个算法创建独立数据实例(如 new ArrayList()、new HashMap())
- 每次测量前重置或重建全部依赖对象,不复用上一轮残留状态
- 避免在被测代码中打印日志、创建临时字符串、触发 GC(如避免循环内拼接字符串)
防止 JIT 优化掉计算逻辑
如果算法结果未被使用,JIT 可能直接优化掉整段计算——你测的其实是“空操作”,而非真实逻辑。
- 将关键计算结果赋值给
volatile变量(如volatile int sink;),强制 JVM 保留该副作用 - 或让结果参与后续不可省略的逻辑,例如作为 return 值、用于 if 判断、传入另一个必须执行的方法
- 简单验证:把算法换成
return 42;,如果耗时骤降为 0 或接近 0,说明原逻辑已被优化
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











