System.nanoTime()最适合性能压测,因其具备纳秒级高分辨率、单调性保障(不受NTP或手动调时影响)、无时区开销且调用极快;而currentTimeMillis()仅毫秒级且易受系统时钟调整干扰。

System.nanoTime() 是 Java 中获取纳秒级时间戳最常用、最可靠的方式,但它不是“绝对时间”,而是以任意起点为基准的**单调递增时钟**,适合做差值计算(如耗时测量),不适合做时间戳或跨进程对时。
为什么 nanoTime 更适合性能压测?
对比 System.currentTimeMillis():
- 高分辨率:通常可达纳秒级(底层依赖 OS 高精度计时器,如 Linux 的 clock_gettime(CLOCK_MONOTONIC)),而 currentTimeMillis() 一般只有毫秒级,Windows 下甚至可能低至 10–16ms
- 单调性保障:不受系统时钟调整(如 NTP 校时、手动改时间)影响,避免压测中因时间跳变导致负耗时或统计失真
- 无时区/历法开销:纯硬件/内核计数器,调用开销极小(通常几十纳秒),适合高频打点
正确使用 nanoTime 的关键细节
常见误用会引入误差甚至崩溃:
- 别直接打印或存储 nanoTime 值:它没有物理意义,只用于相减。例如 System.nanoTime() + "ns" 是无效信息
- 避免 long 溢出但不必过度担心:nanoTime 约每 292 年溢出一次(2⁶³ ns ≈ 292 年),压测场景完全无需处理
- 两次调用尽量靠近:减少被 GC、线程调度等干扰。例如在方法入口/出口直接记录,不要中间穿插日志或复杂逻辑
- 注意 JIT 预热影响:首次调用可能含类加载、JIT 编译开销,压测前应预热(如空跑 1w 次)再开始采集
压测中实测耗时的推荐写法
以单次请求耗时为例,简洁安全:
long start = System.nanoTime();
try {
// 执行待测逻辑(如 HTTP 调用、算法计算)
doWork();
} finally {
long costNs = System.nanoTime() - start;
// 记录到指标系统(如 Micrometer Timer、自定义 Histogram)
timer.record(costNs, TimeUnit.NANOSECONDS);
}批量压测时建议:
- 用 ThreadLocal
或环形缓冲区暂存样本,避免频繁对象分配 - 统计维度建议保留原始纳秒值,后续统一转为微秒或毫秒展示(1μs = 1000ns,1ms = 1,000,000ns),避免多次除法损失精度
- 关注 P95/P99 而非平均值,尤其在高并发下,尾部延迟更能暴露锁竞争、GC 或 IO 阻塞问题
容易被忽略的干扰项
即使用了 nanoTime,这些因素仍会让结果失真:
- Stop-The-World GC:Full GC 可能暂停应用线程数百毫秒,此时 nanoTime 仍在走,但业务未执行。建议压测时开启 GC 日志(-Xlog:gc*:file=gc.log),排除 GC 集中时段的数据
- CPU 频率动态调节:笔记本或云主机可能降频节能,导致相同代码耗时波动。可固定 CPU 频率(如 Linux 使用 cpupower frequency-set -g performance)
- NUMA 与线程绑定:多路服务器上,跨 NUMA 访存延迟高。压测时用 taskset 绑定线程到特定 CPU 核,并确保数据本地化


















