前言

本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。


Java 线程的状态模型:Java 状态是表象,JVM 内部状态是逻辑,OS 状态才是物理现实

Java 线程的状态模型本质上是一种“抽象包装”。JVM 为了屏蔽不同操作系统的差异,定义了一套高度统一的逻辑状态,而底层 OS 线程状态则是真实反映 CPU 调度和资源竞争的物理状态。

正式由于这种“抽象包装”和差异的屏蔽,要想全面的理解线程状态不仅仅是看到 Thread.State 枚举的表象,而是要看穿 Java 虚拟机(JVM) 如何将高级抽象的逻辑状态映射到 操作系统(OS) 的调度实体上。在 OpenJDK8中,这种对应关系并非一一对应,甚至在某些状态下存在“多对一”的掩盖现象。

要看清底层真相,我们需要对照三个层面的定义:

  1. Java 层java.lang.Thread.State 定义的 6 种状态。

  2. JVM 内部层hotspot/src/share/vm/runtime/osThread.hpp 中的 ThreadState定义,记录了线程在 JVM 内部的详细处境(如 MONITOR_WAIT, CONDVAR_WAIT)。

  3. OS 层 (Linux):对应 task_struct 中的 state 字段(如 TASK_RUNNING, TASK_INTERRUPTIBLE)。在运行时由/proc/[pid]/status 中记录的进程状态(R, S, D, T, Z)。


1. 核心对应关系全景图

Java 线程状态 (Thread.State) JVM 内部状态 (OSThread::ThreadState) Linux 内核状态 (Task State) 触发场景与底层细节
NEW ALLOCATED 不存在 / N/A 仅创建了 Java 对象,尚未调用 start0(),无物理线程。
RUNNABLE RUNNABLE TASK_RUNNING ( R) 包含正在运行与就绪(Ready)。OS 调度器视角下它们是一样的。
BLOCKED MONITOR_WAIT TASK_INTERRUPTIBLE (S) 特指进入 synchronized 块失败,在 JVM 内部的 ObjectMonitor 队列中等待。底层通常是 futex 系统调用。
WAITING OBJECT_WAIT / PARKED TASK_INTERRUPTIBLE (S) Object.wait()LockSupport.park()。底层调用 pthread_cond_wait
TIMED_WAITING OBJECT_WAIT / PARKED TASK_INTERRUPTIBLE (S) 带有超时参数的等待。底层调用 pthread_cond_timedwait
TERMINATED ZOMBIE / EXITED EXIT_ZOMBIE (Z) 线程执行完毕,但在 C++ JavaThread 对象销毁前短暂存在。

2. 深度源码级解析

A. NEW (新建)
  • Java 源码Thread 对象已被实例化,但尚未调用 start()
  • JVM 内部:对应的 C++ JavaThread 尚未创建,或者处于 _thread_new 初始状态。
  • OS 状态:不存在对应的物理线程。
  • 专家视角:此时仅仅是一个 Java 堆上的对象,没有分配 1MB 的系统栈。
B. RUNNABLE:Java 最大的“谎言”

这是 Java 状态模型中最具“欺骗性”的一个状态。在 Java 看来,只要线程没有被阻塞在锁或 I/O 上,它就是 RUNNABLE

  • Java 源码:正在 JVM 中执行,或者正在等待操作系统的资源(如 CPU)。

  • OS 状态:对应 Linux 的 TASK_RUNNING

  • 关键细节:Linux 的 TASK_RUNNING 实际上包含了“正在运行”和“就绪等待调度”两种情况。JVM 并不区分这二者,因为它将线程调度权完全交给了 OS 调度器(CFS)。

  • 特殊情况:当线程执行纯计算任务或在用户态执行 JNI 代码时,它始终是 RUNNABLE

    java.lang.Thread.State 中,RUNNABLE 状态是一个巨大的集合。

  • 源码细节:在 hotspot/src/share/vm/runtime/osThread.cpp 中,一旦 pthread_create 成功,JVM 就会认为线程进入了可运行状态。

  • 专家视点:Java 的 RUNNABLE 状态并不保证线程真的在占用 CPU。它合并了 OS 层的 Ready(在就绪队列等待 CPU)和 Running(正在 CPU 上执行)。如果你在 Linux 下看到线程处于 R 状态但程序很慢,那通常是由于 CPU 竞争激烈导致的“就绪等待”。

小结
在 Java 层面,只要线程没有阻塞在锁或 I/O 上,它就是 RUNNABLE。但在 OpenJDK 源码中,这细分为两种情况:

  1. 处于 Java 态 (_thread_in_Java):正在执行字节码。
  2. 处于 Native 态 (_thread_in_native):正在执行 JNI 代码。
    专家视点:OS 层面,RUNNABLE 既包含正在 CPU 上跑的(Running),也包含在就绪队列等 CPU 的(Ready)。JVM 并不区分这两者,因为这是 OS 内核的职责,JVM 无权干涉。
C. BLOCKED vs WAITING:两种不同的“睡姿”

这是在实战中最容易混淆的点。

  • BLOCKED (JVM 层面的阻塞)

    在 OpenJDK 8 中,BLOCKED 状态仅限于原生 synchronized 关键字。当线程尝试进入监视器(Monitor)失败时,它会被挂起到 _EntryList 队列中。此时在 OS 层面,它处于睡眠状态(S),等待 futex 信号。

    • Java 源码:线程正在等待监视器锁(synchronized 关键字)。
    • JVM 内部:对应 src/share/vm/runtime/osThread.hpp 中的 MONITOR_WAIT
    • OS 状态TASK_INTERRUPTIBLE
    • 核心逻辑:在 OpenJDK 8u44 中,当线程争抢 ObjectMonitor 失败时,会进入 ObjectMonitor::EnterI 方法,最终调用 os::PlatformEvent::park(),使线程在系统层挂起。
  • WAITING (逻辑层面的等待)

    当调用 LockSupport.park()(如 ReentrantLock 的底层实现)或 Object.wait() 时,线程状态是 WAITING

    • Java 触发Object.wait(), Thread.join(), LockSupport.park()
    • JVM 内部:对应 CONDVAR_WAIT (条件变量等待) 或 OBJECT_WAIT
    • OS 状态TASK_INTERRUPTIBLE
    • 专家视角:即使是 java.util.concurrent 下的锁(如 ReentrantLock),由于其底层基于 LockSupport.park(),在 Java 状态机中显示为 WAITING 而非 BLOCKED。这是因为 park 调用在 JVM 内部被视为一种“无理由”的挂起。
  • 本质区别
    在源码 hotspot/src/share/vm/runtime/objectMonitor.cpp 中可以看到,BLOCKED 是由 JVM 内核通过互斥锁直接管理的;而 WAITING 通常是 Java 类库主动要求的挂起。在 OS 看来,它们大多都是 TASK_INTERRUPTIBLE

D. TERMINATED (终止)
  • Java 源码run() 方法执行结束。
  • JVM 内部:线程执行 JavaThread::exit,状态变为 _thread_terminated
  • OS 状态EXIT_ZOMBIE 或已销毁。

3. 特殊状态:I/O 阻塞到底是什么状态?

这是必须掌握的冷知识。当一个 Java 线程执行阻塞式网络 I/O 或磁盘 I/O 时,Java 状态是什么?

  • 答案是:RUNNABLE。
  • 底层逻辑:当线程发起 read()write() 系统调用时,CPU 可能会切走,OS 状态会变为 TASK_INTERRUPTIBLE (S)TASK_UNINTERRUPTIBLE (D)(磁盘等待)。
  • JVM 的处理:由于 I/O 操作是由内核接管的,JVM 认为该线程仍然在“执行”native 代码,因此 java.lang.Thread 保持 RUNNABLE
  • 诊断坑点:这意味着你不能通过 jstack 看到线程是否卡在 I/O 上,因为它们看起来都在运行。你必须观察 Linux 的 wchan 或使用 strace

4. 源码中的状态切换“桥梁”:_thread_blocked

在 OpenJDK 8u44 内部,有一个非常关键的宏定义和枚举:JavaThreadState(位于 globalDefinitions.hpp)。

// hotspot/src/share/vm/utilities/globalDefinitions.hpp
enum JavaThreadState {
  _thread_uninitialized     =  0,
  _thread_new               =  2,
  _thread_in_native         =  4, // 正在执行 JNI 代码
  _thread_in_vm             =  6, // 正在执行 JVM 内部 C++ 代码
  _thread_in_Java           =  8, // 正在执行 Java 字节码
  _thread_blocked           = 10  // 线程因为某种原因被阻塞(安全点、锁等)
};

专家级结论

任何 Java 层的“等待”(BLOCKED, WAITING, TIMED_WAITING)在 JVM 内核代码执行时,几乎都会经历一个名为 ThreadStateTransition 的包装器,将状态暂时切为 _thread_blocked。这是为了告诉 GC (垃圾回收器):“这个线程现在没在动,你可以安全地进行安全点(Safepoint)操作,不用等它了”。


5. 实战诊断路线

  1. jstack 发现大量 BLOCKED
    直接查 synchronized 关键字。在 OpenJDK 8 中,这通常意味着偏向锁撤销导致的竞争或在高并发下的对象监视器瓶颈
  2. jstack 发现大量 WAITING (on object monitor)
    检查 Object.wait() 调用,通常是生产者-消费者模式中的缓冲区满了/空了。
  3. jstack 发现大量 WAITING (parking)
    检查 java.util.concurrent 的锁或线程池。如果此时系统吞吐量低,可能存在锁竞争或线程池任务积压。
  4. CPU 飙升但 jstack 全是 RUNNABLE
    说明线程正在真实的执行逻辑(死循环、复杂计算)或处于频繁的 Safepoint 轮询中。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐