java线程io阻塞时jvm状态仍为runnable,因该状态涵盖操作系统就绪与运行态,且不区分底层i/o等待;需结合ps、strace、jstack等工具交叉验证os级阻塞状态。

Java 中线程在 IO 上挂起时,**不能单靠 Thread.getState() 返回的 RUNNABLE 就判断它没被阻塞**——这是最容易踩的坑。真实情况是:JVM 层面线程状态显示为 RUNNABLE,但操作系统层面该线程可能早已休眠等待数据,CPU 并未执行它。
看 JVM 线程状态(表象)
调用 thread.getState() 或用 jstack <pid></pid> 查看,常见结果是:
-
socket.read()、InputStream.read()、BufferedReader.readLine() 等传统 BIO 调用中,线程状态通常显示为
RUNNABLE - 这不代表线程正在活跃运行,而是 JVM 认为“它没在等锁、也没显式 sleep/wait”,所以归入可运行态
-
BLOCKED状态只用于锁竞争;WAITING/TIMED_WAITING用于wait()、join()、sleep()等显式控制,不涵盖底层系统级 IO 阻塞
查操作系统级线程行为(本质)
要确认是否真被 IO 挂起,得穿透 JVM 看 OS 级线程状态:
- Linux 下用
ps -T -p <pid></pid>查所有 LWP(轻量级进程),再结合cat /proc/<tid>/stat</tid>看第 3 列(state):D 表示 uninterruptible sleep(常因 IO),S 表示 interruptible sleep(可能含 IO 等待) - 用
strace -p <tid></tid>可直接看到线程卡在哪个系统调用上,例如recvfrom(12, ...)或read(15, ...)—— 这就是 IO 阻塞的铁证 -
jstack输出中若看到线程堆栈停留在java.net.SocketInputStream.socketRead0或类似 native 方法,配合 OS 状态,基本可断定是 IO 阻塞
用工具链交叉验证(实操建议)
单靠一个命令容易误判,推荐组合使用:
- 先用
jstack <pid> > jstack.out</pid>抓快照,搜索java.net或readLine关键字,定位可疑线程 ID(nid) - 再用
jstack输出里的十六进制 nid 转十进制,对应到ps -T -p <pid></pid>中的 tid,查其 OS 状态 - 对高嫌疑线程执行
strace -p <tid> -e trace=recvfrom,read,recv</tid>,观察是否长时间无返回 - 生产环境可配合
async-profiler采样,查看线程在java.net.SocketInputStream.socketRead0的热点耗时占比
区分 BIO 与 NIO 的表现差异
阻塞感知方式也取决于 IO 模型:
-
BIO(如
Socket.getInputStream().read()):几乎总是表现为 JVMRUNNABLE+ OSD/S状态,需靠外部工具识别 -
NIO(如
channel.read(buffer)配合Selector):若用的是非阻塞模式(configureBlocking(false)),线程不会挂起,read()直接返回 0 或 -1;只有在阻塞模式下才出现同 BIO 的挂起现象 - 虚拟线程(Project Loom)中,
Thread.State仍可能为RUNNABLE,但 JVM 内部会自动挂起并复用 OS 线程,此时更需依赖jcmd <pid> VM.native_memory</pid>或jfr追踪调度行为
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











