System.nanoTime() 可准确测量包含IO的端到端耗时,但测得的是“发起IO请求→内核完成→返回Java层”的完整延迟,需严格包裹阻塞调用、分段计时并避免GC与调度干扰。

System.nanoTime() 本身不能区分“计算耗时”和“IO等待耗时”,但它可以准确测量包含 IO 操作在内的**端到端时间差**——关键在于你如何定义“要度量的时长”。如果你明确想测 IO 等待本身(比如一次 read() 阻塞了多久),nanoTime 是可用的,但必须理解它测的是“从调用开始到返回”的总耗时,其中混杂了内核调度、驱动响应、设备延迟等,而非纯用户态 CPU 时间。
IO 操作的耗时本质是阻塞等待
Java 中的典型 IO(如 FileInputStream.read()、SocketChannel.read()、JDBC 查询)在底层会触发系统调用,线程进入阻塞状态。这段时间内 CPU 不执行你的 Java 代码,但 nanoTime 依然持续计数。因此:
- 它测出的是“发起 IO 请求 → 内核完成 → 返回 Java 层”的完整延迟,即真实感知到的 IO 延迟
- 这个值对性能调优有意义:能暴露慢磁盘、高延迟网络、数据库锁争用等问题
- 但它不是“CPU 花费”,也不是可优化的纯算法开销——你优化不了网卡响应时间,但能换 SSD 或加缓存
正确包裹 IO 调用,避免污染测量
把 nanoTime 放在 IO 方法调用的紧邻前后,不掺杂其他逻辑:
- ✅ 正确:long start = System.nanoTime(); int n = inputStream.read(buffer); long end = System.nanoTime();
- ❌ 错误:在 read() 前做日志打印、参数校验;在 read() 后立即做 String 构造或 JSON 解析——这些属于后续处理,不应计入 IO 等待
- ⚠️ 注意:若使用 NIO 的非阻塞模式(如 Selector + channel.configureBlocking(false)),read() 可能立即返回 -1 或 0,此时测得的是“检查就绪状态”的开销,不是传统 IO 等待
区分 IO 等待与业务处理,需分段计时
一个完整流程常含“等待 IO”+“处理数据”,建议拆开测量:
立即学习“Java免费学习笔记(深入)”;
- IO 等待段:只包 read()/write()/executeQuery() 这类阻塞调用
- 业务处理段:如 new String(buffer)、Jackson.parse()、DTO 转换等纯内存操作
- 示例:
long ioStart = System.nanoTime();
byte[] data = httpInputStream.readAllBytes(); // IO 阻塞点
long ioEnd = System.nanoTime();
long procStart = System.nanoTime();
JsonNode node = mapper.readTree(data); // CPU 密集解析
long procEnd = System.nanoTime();
注意干扰项:不要让 GC 或调度掩盖 IO 特征
IO 操作本身不触发 GC,但大缓冲区分配(如 new byte[8192])可能间接引发 Minor GC。为聚焦 IO 行为:
- 复用缓冲区(ByteBuffer.allocateDirect 或池化 byte[]),避免每次 IO 都分配对象
- 不在线程中混入日志输出、监控打点等额外操作——它们可能引入锁或 GC
- 若发现某次 read() 耗时异常高(如几十毫秒),优先查系统层面:磁盘 IOPS、网络丢包、数据库连接池满、DNS 解析慢等


















