Java线程自述前言Java线程之我的诞生第一阶段对象创建期Java 层面第二阶段启动触发期JNI 到 JVM 内部1. 进入 JNI 入口2. 创建 C JavaThread 对象3. 申请系统资源os::create_thread第三阶段新线程的“觉醒”内核返回 JVM1. 线程入口点thread_native_entry2. 状态切换与同步3. 进入 JVM 运行期4. 跃入 Java 世界call_stub完整的调用链清单Java线程诞生链路总结C 调用链全景图Java线程之我的寿终正寝1. 终点的起点JavaThread::exit2. 移除影子注销 JNI 与 监视器3. 物理肉身的消亡os::terminate_thread4. 幽灵状态JavaThread 对象的最后时刻5. 异常导致的死亡未捕获异常处理总结反向调用链番外篇核心真相为什么不能重复 start()守护线程Daemon的特殊待遇前言本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限文中内容难免存在疏漏恳请读者不吝指正。Java线程之我的诞生我Java线程的诞生是一个极其壮观的过程。从 Java 层的new Thread()到 CPU 开始执行run()方法是一场跨越了Java、C、POSIX 线程库pthread以及 Linux 内核的接力赛。我们可以将这个过程拆解为三个阶段对象创建期、启动触发期、以及新线程的“觉醒”。第一阶段对象创建期Java 层面当你执行new Thread()时JVM 内部其实非常平静并没有触及任何系统线程。Java 构造函数执行java.lang.Thread的构造函数init()。此时JVM 只是在 Java 堆上分配了一个普通的 Java 对象。设置属性初始化线程名、优先级、守护状态等。注意此时没有任何C 层的线程被创建也没有任何操作系统资源被申请。真相此时在操作系统看来这只是一个包含几个变量的普通 Java 对象没有任何执行权。第二阶段启动触发期JNI 到 JVM 内部真正的魔法始于thread.start()。此时执行start()的父线程通常是主线程会进入 JVM 内部为你代劳负责在 C 层面为你“捏”出一个对应的肉身。1. 进入 JNI 入口Thread.start()调用了 native 方法start0()对应源码中的源码位置src/share/vm/prims/jvm.cpp函数JVM_StartThread核心逻辑检查线程状态。如果线程已经启动过直接抛出异常。2. 创建 CJavaThread对象JVM_StartThread会在 C 堆上创建一个JavaThread实例。源码位置src/share/vm/runtime/thread.cpp函数new JavaThread(thread_entry, sz)// 关键调用new 一个 C 对象native_threadnewJavaThread(thread_entry,sz);* 这个 C 对象是 Java 线程在 JVM 里的“影子”。它初始化了内部锁SR\_lock和各种状态。 * **核心调用**: os::create_thread(this, thr_type, stack_sz);核心逻辑在 C 堆上创建一个重量级的JavaThread对象。这个对象是连接 Java 对象与 OS 线程的桥梁。3. 申请系统资源os::create_threadJavaThread的构造函数会调用平台相关的创建函数。以 Linux 为例文件src/os/linux/vm/os_linux.cpp函数os::create_thread核心逻辑创建一个OSThread对象。调用 Linux 的线程库pthread_create。此时Linux 内核分配一个 LWP轻量级进程一条真实的物理线程正式诞生。第三阶段新线程的“觉醒”内核返回 JVM此时Linux 内核已经创建了一个新的 LWP轻量级进程。但这个线程并不会立刻跑 Java 代码它首先要进行“环境整备”。1. 线程入口点thread_native_entry新线程启动后首先执行的是 JVM 预设的 C 静态函数文件os_linux.cpp函数thread_native_entry核心逻辑初始化线程的Stack Anchor栈锚点。设置Thread Local Storage (TLS)让 CPU 随时能通过寄存器找到当前的JavaThread指针。设置栈警戒页Yellow/Red Zone。握手等待父线程完成初始化信号确保自己已经完全准备好。2. 状态切换与同步新线程会在一个信号量SyncCondition上等待直到父线程执行start()的线程完成所有的设置并发出指令。3. 进入 JVM 运行期函数JavaThread::run()-thread_main_inner()核心逻辑将线程状态从_thread_new切换为_thread_in_vm。4. 跃入 Java 世界call_stub一旦准许运行新线程会调用源码位置hostspot/src/share/vm/runtime/javaCalls.cpp函数JavaCalls::call_virtual核心逻辑JVM 找到java.lang.Thread.run()方法的字节码地址。调用我们之前深聊过的call_stub汇编片段。call_stub压入 C 寄存器移动RSP指针建立第一个 Java 栈帧。跳转CPU 执行流正式进入 Java 字节码解释器。调用java.lang.Thread.run()。完整的调用链清单我们可以根据源码路径总结出Java线程辉煌的一生Java线程诞生链路Java:thread.start()JNI:JVM_StartThread(jvm.cpp)JVM:new JavaThread()(thread.cpp)OS:os::create_thread(os_linux.cpp)C-Lib:pthread_create(进入 Linux 内核)----- 线程切换新线程开始工作 -----OS:thread_native_entry(os_linux.cpp)JVM:JavaThread::run()(thread.cpp)JVM:thread_main_inner()Assembly:JavaCalls::call_virtual(通过call_stub压栈)Java:Thread.run()(执行你写的业务逻辑)总结C 调用链全景图[父线程执行] Thread.start() - JVM_StartThread (jvm.cpp) - new JavaThread() (thread.cpp) - os::create_thread (os_linux.cpp) - pthread_create (Linux Kernel) [新线程执行] thread_native_entry (os_linux.cpp) - JavaThread::run (thread.cpp) - thread_main_inner - JavaCalls::call_virtual (javaCalls.cpp) - call_stub (汇编机器码) - Thread.run() (你的代码)Java线程之我的寿终正寝1. 终点的起点JavaThread::exit当run()方法返回控制权回到thread_main_inner。随后线程会进入核心清理函数JavaThread::exit。文件src/share/vm/runtime/thread.cpp第一步触发 Java 层的“告别”JVM 会调用java.lang.Thread类中的exit()方法。真相这会清理ThreadGroup中的引用并将threadTerminated字段设为true。此时如果在 Java 层调用isAlive()将开始返回false。2. 移除影子注销 JNI 与 监视器在 C 层JavaThread::exit继续深入执行清理 JNI 句柄释放该线程持有的所有局部 JNI 引用HandleMark。释放所有权锁如果线程死的时候还抓着某些synchronized锁通常由于非正常退出JVM 会强制释放它们防止死锁。同步 Join 节点这是最关键的一步。// 源码逻辑简化this-set_thread_state(_thread_terminated);ensure_join(this);// 唤醒所有在等待这个线程 die 的父线程join()JVM 会触发该线程关联的lock对象实际上是java.lang.Thread对象本身的notifyAll()通知那些在thread.join()上苦苦等待的线程。3. 物理肉身的消亡os::terminate_thread一旦 JVM 层的逻辑处理完毕线程就开始销毁它的“物理外壳”。销毁OSThread释放 C 层的操作系统线程包装器。移除 TLS清理线程本地存储告诉系统这个pthread不再与任何JavaThread绑定。栈内存的归还真相JVM 并不手动调用free来释放栈。由于栈是由pthread_create分配的当thread_native_entry这个 C 函数最终执行完return时底层的POSIX 线程库GLIBC会接管。如果线程是joinable的内存会在父线程join后回收如果是detached的内存在退出时立即由 OS 回收。4. 幽灵状态JavaThread对象的最后时刻你可能会发现即便线程运行结束了那个java.lang.Thread对象可能还在堆里。Java 对象只要还有人引用它它就死不了直到下次 GC。C JavaThread 对象在exit函数的末尾它会执行delete this。注意在 OpenJDK 8 中主线程Main Thread的销毁略有不同它会一直等待直到所有非守护线程都退出才调用DestroyJavaVM。5. 异常导致的死亡未捕获异常处理如果线程是因为异常死的清理链路会多出一个“遗言”环节寻找处理器检查线程是否设置了UncaughtExceptionHandler。分级上报如果没有则交给ThreadGroup最后交给 JVM 默认的处理器通常是打印堆栈。强制进入 exit无论异常多严重JVM 最终都会强行拉回JavaThread::exit流程确保系统资源如锁、栈、内存不会泄露。总结反向调用链Java:Thread.run()执行完毕返回。C: 进入JavaThread::exit()。JVM: 唤醒join()上的等待者。OS: 清理OSThread和TLS。C-Lib:pthread退出操作系统回收那1MB 的物理栈。番外篇核心真相1:1 映射在 OpenJDK每一个 Java 线程在底层都有一个完整的 CJavaThread对象和一个 Linuxpthread与之对应。eetop 字段Java 层的Thread类通过一个叫eetop(End-to-End Thread Object Pointer) 的long类型字段在内存中保存了 C 层JavaThread对象的地址。权限分明new Thread()只是申请了“灵魂”Java 对象start()才是通过内核去申请“肉身”OS 线程和物理栈。为什么不能重复 start()在JVM_StartThread的源码开头有一行极其严厉的检查if(java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread))!NULL){throw_illegal_thread_statetrue;}真相一旦JavaThread指针被绑定到了 Java 的Thread对象上即eetop字段不再为 0JVM 就会认定这个线程已经“出生”过绝对不允许第二次启动。守护线程Daemon的特殊待遇为什么main线程结束了守护线程会被“秒杀”真相当最后一个非守护线程通常是main调用DestroyJavaVM时它并不等待守护线程执行完JavaThread::exit()。它会直接调用操作系统的exit()。这意味着守护线程往往连 C 层的清理逻辑都跑不完就会被操作系统直接从内存中抹除。这就是为什么你永远不应该在守护线程里写“必须执行”的写文件或关流操作。