不能直接用 system.nanotime() 做生产环境性能评估,因其无统计能力、无跨jvm可比性、不兼容监控系统;需配合无锁环形缓冲区、纳秒直方图、gc干扰剔除等机制才能准确反映尾部延迟。

为什么不能直接用 System.nanoTime() 做生产环境性能评估
它本身不提供统计能力,只返回一个纳秒级长整型值;单独调用两次相减只能得到单次耗时,而生产环境的延迟分布高度偏态(P99 可能是 P50 的 10 倍以上),靠平均值或单点采样会严重掩盖尾部问题。更关键的是:System.nanoTime() 的值没有物理时间意义,不能跨 JVM、不能存日志做回溯比对,也不能直接喂给 Prometheus 这类指标系统。
必须搭配环形缓冲区 + 无锁写入
高频打点(比如每秒万级请求)下,频繁 new 对象或加锁收集会导致 GC 压力飙升、线程阻塞,反而污染测量结果。实操建议:
- 用
ThreadLocal绑定一个固定大小的long[]数组(如 4096 元素),每次写入用原子自增索引取模定位,避免扩容和竞争 - 拒绝使用
ConcurrentLinkedQueue或BlockingQueue:它们的 CAS 开销在微秒级,比一次System.nanoTime()调用还重 - 缓冲区满后采用“覆盖写”而非丢弃——P99/P999 的样本往往藏在连续高峰末尾,丢弃逻辑会系统性低估尾部延迟
- 每 10 秒或每 1024 个样本触发一次批量 flush,转成直方图(histogram)结构上报,别传原始数组
直方图分桶必须用纳秒原值,别提前除法
把 durationNs 直接映射到预设纳秒区间(如 [0, 1000), [1000, 10000), [10000, 100000) …),而不是先除以 1000 转毫秒再分桶。原因有二:
- 整数除法截断误差:1499ns / 1000 = 1ms,但实际属于 [1000, 10000) 纳秒桶,归错桶后 P99 计算偏差可达 900ns 以上
- CPU 指令级开销:每次除法多 1–2 个周期,百万次调用就是可观延迟,且编译器未必能优化掉重复除法
- 下游聚合(如 Micrometer 的
DistributionSummary)默认接收纳秒单位,传毫秒需额外配置单位转换,易漏配
GC 和 STW 是最大干扰源,必须剥离
哪怕用了 System.nanoTime(),Full GC 或 ZGC 的 Stop-The-World 阶段仍会让线程暂停,此时纳秒计数器照常走,但业务逻辑完全卡住——你测到的是“挂起时间 + 执行时间”,不是真实耗时。应对方式:
- 压测或监控期间必须开启 GC 日志:
-Xlog:gc*:file=gc.log:time,uptime,level,tags - 采集耗时的同时,用
ManagementFactory.getGarbageCollectorMXBeans()检查getCollectionCount()和getCollectionTime()是否突增 - 对齐时间戳:把 GC 暂停窗口(如
GC pause (G1 Evacuation Pause)的 start time 和 end time)转换为纳秒,剔除所有落在该区间内的样本 - 注意:ZGC 的
Pause时间极短(Concurrent Cycle 阶段不 STW,无需剔除










