Java线程创建过程剖析
线程创建过程
前言
本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。
Java线程创建过程
一、 整体调用链路概述
在 OpenJDK 8中,Java 线程(java.lang.Thread)的创建与启动是一次典型的跨语言、跨内核边界的协作过程。其核心逻辑并非在 Java 中实现,而是通过 JNI(Java Native Interface)下沉到 JVM 的 C++ 运行时,最终通过 glibc 库调用 Linux 内核的 clone() 系统调用来创建轻量级进程(LWP)。
整体调用链条如下:
[Java 层] Thread.start()
└───> Thread.start0() (Native 方法)
│
[JNI 层] Thread.c -> JVM_StartThread
│
[JVM 层] jvm.cpp -> JVM_StartThread()
└───> thread.cpp -> new JavaThread()
└───> os_linux.cpp -> os::create_thread()
│
[OS/库层] └───> glibc -> pthread_create()
└───> Linux 内核 -> clone() 系统调用
二、 Java 层与 JNI 桥接层
Java 层的 Thread.start() 是线程生命的起点。为了防止线程被重复启动,内部维护了一个 threadStatus 状态机。
1. Java 源码:src/share/classes/java/lang/Thread.java
public synchronized void start() {
// 如果线程状态不是 "NEW" (0),说明已经启动过或已结束,直接抛出异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 通知线程组,该线程即将被启动,将其加入线程组的未启动线程计数中
group.add(this);
boolean started = false;
try {
// 调用底层 Native 方法,进入 JVM 内部
start0();
started = true; // 标记启动成功
} finally {
try {
if (!started) {
// 如果启动失败,通知线程组进行清理,并递减未启动线程计数
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// 忽略清理期间抛出的异常
}
}
}
// 核心 Native 方法映射
private native void start0();
2. JNI 映射源码:jdk/src/share/native/java/lang/Thread.c
JVM 在启动时会加载核心类库,并通过 registerNatives 将 Java 的 start0 映射到 C++ 层的 JVM_StartThread 函数。
#include "jni.h"
#include "jvm.h"
#include "java_lang_Thread.h"
// 将 Java 方法名、方法签名与 C++ 函数指针进行绑定
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
// ... 其他 native 方法省略
};
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls) {
// 注册本地方法映射表
(*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(JNINativeMethod));
}
三、 JVM 核心业务逻辑层
当 start0() 被触发后,执行流程流转到 JVM 容器内(HotSpot 虚拟机)。此处完成 JavaThread 对象的构建、栈内存分配分配以及与底层 OS 线程的绑定。
1. JVM 入口源码:hotspot/src/share/vm/prims/jvm.cpp
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
// 1. 检查 Java 线程对象是否已经被创建过底层 C++ 线程(双重检查)
bool throw_illegal_thread_state = false;
{
// 获取 Threads_lock 互斥锁,保证线程安全
MutexLocker mu(Threads_lock);
// 如果对应的 C++ JavaThread 已经存在,说明该线程已经 start 过了
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
// 2. 获取 Java 线程对象中定义的栈大小(如果在 new Thread 时传入了 stackSize)
jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz = size > 0 ? (size_t)size : 0;
// 3. 创建核心的 C++ JavaThread 实例
// thread_entry 为线程的入口包装函数,后续会回调 Java 的 Thread.run()
native_thread = new JavaThread(&thread_entry, sz);
// 如果分配内存失败(如 OOM 或无法分配内核线程),native_thread 会为 NULL
if (native_thread->osthread() != NULL) {
// 建立 Java 层 Thread 对象与 C++ 层 JavaThread 对象的双向绑定
native_thread->prepare(jthread);
}
}
}
// 4. 异常处理:抛出非法的线程状态异常
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
// 5. 再次校验 C++ 线程以及底层的 OSThread 是否成功创建
if (native_thread == NULL || native_thread->osthread() == NULL) {
if (native_thread != NULL) {
// 释放内存并清理
delete native_thread;
}
// 抛出最常见的:无法创建本地线程的 OutOfMemoryError
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(), "unable to create new native thread");
}
// 6. 将线程状态设置为 ALLOCATED,并正式启动底层的 OS 线程
native_thread->set_thread_state(_thread_in_vm);
Thread::start(native_thread);
JVM_END
2. JavaThread 构造源码:hotspot/src/share/vm/runtime/thread.cpp
在 new JavaThread 的构造函数中,HotSpot 会调用操作系统相关的接口去执行真正的线程创建。
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread() {
// 初始化 JavaThread 的内部成员变量
initialize();
_anchor.clear();
_entry_point = entry_point; // 保存线程入口(即 thread_entry)
// 设置线程类型为 java_thread
os::ThreadType thr_type = os::java_thread;
// 核心步骤:调用操作系统抽象层 (OS Layer) 的创建接口
// 将当前的 JavaThread 指针传过去,以便底层 OS 线程能与之关联
bool os_alloc_result = os::create_thread(this, thr_type, stack_sz);
if (!os_alloc_result) {
// 如果 OS 分配失败,直接返回,上层检测到 osthread() 为 NULL 就会抛出 OOM
return;
}
}
四、 操作系统抽象层 (Linux 特化)
HotSpot 针对不同的操作系统(Linux、Windows、Solaris)有不同的实现。在 Linux 环境下,对应的是 os_linux.cpp。
源码:hotspot/src/os/linux/vm/os_linux.cpp
在 os::create_thread 中,JVM 会调用 POSIX 线程库(glibc 的 NPTL)的 pthread_create。
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
assert(thread->osthread() == NULL, "caller responsible");
// 1. 分配一个 OSThread 对象,用来记录操作系统层面的线程信息(如 tid, 句柄等)
OSThread* osthread = new OSThread(NULL, NULL);
if (osthread == NULL) {
return false; // 内存分配失败
}
// 设置初始线程状态为 ALLOCATED
osthread->set_state(ALLOCATED);
// 将 OSThread 绑定到 JavaThread 对象中
thread->set_osthread(osthread);
// 2. 初始化 pthread 属性对象
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置为分离状态,线程退出后自动释放资源
// 3. 计算并设置线程栈大小
stack_size = os::Linux::default_stack_size(thr_type);
if (stack_size > 0) {
pthread_attr_setstacksize(&attr, stack_size);
}
// 4. 关键:调用 glibc 的 pthread_create 创建线程
// &tid: 接收创建成功的 posix 线程 id
// &attr: 线程属性(栈大小、分离状态等)
// java_start: C++ 层的启动哨兵函数(负责引导并同步线程状态)
// thread: 将当前的 JavaThread 对象指针作为参数传给 java_start
pthread_t tid;
int ret = pthread_create(&tid, &attr, &java_start, thread);
// 5. 收尾工作与状态同步
pthread_attr_destroy(&attr);
if (ret != 0) {
// 倘若 pthread_create 返回非0,说明操作系统拒绝创建新线程(如超过 max user processes)
thread->set_osthread(NULL);
delete osthread;
return false;
}
// 保存 posix 线程 ID 到 OSThread
osthread->set_pthread_id(tid);
return true;
}
五、 线程启动与回调生命周期
通过 pthread_create 创建的线程并不会立即疯狂执行 Java 代码,它需要经过 JVM 的初始化同步。
1. 哨兵启动函数:java_start (os_linux.cpp)
新诞生在内核之中的线程,其执行的第一个 C++ 函数就是 java_start。
static void *java_start(Thread *thread) {
// 1. 将当前底层线程的 pid (Process ID, Linux 下即轻量级进程的 TID) 存入 OSThread
OSThread* osthread = thread->osthread();
osthread->set_thread_id(os::current_thread_id());
// 2. 初始化底层 OS 信号处理(比如用于处理内存断言、处理线程中断信号等)
os::Linux::hotspot_sigmask(thread);
os::Linux::init_thread_fpu_state();
{
// 3. 线程状态同步控制
// 此时新线程已经就绪,但必须挂起等待,直到父线程(调用 start() 的线程)明确发出指令
MutexLockerEx ml(osthread->startThread_lock(), Mutex::_no_safepoint_check_flag);
// 将状态从 INITIALIZED 变更为 RUNNABLE
osthread->set_state(INITIALIZED);
// 通知父线程:我已经初始化完毕了
osthread->startThread_lock()->notify_all();
// 循环等待:直到父线程在 Thread::start 里面将状态改写成更为高级的状态(或发出信号)
while (osthread->get_state() == INITIALIZED) {
osthread->startThread_lock()->wait(Mutex::_no_safepoint_check_flag);
}
}
// 4. 真正破茧而出,调用 JavaThread 的 run 方法
thread->run();
return 0;
}
2. 父线程的放行信号:Thread::start (thread.cpp)
回到父线程的视角。在 jvm.cpp 中,最后一步调用了 Thread::start(native_thread),这里才是激活子线程的开关。
void Thread::start(Thread* thread) {
trace("start", thread);
// 获取子线程的 startThread_lock
OSThread* osthread = thread->osthread();
if (osthread != NULL) {
MutexLockerEx ml(osthread->startThread_lock(), Mutex::_no_safepoint_check_flag);
// 将子线程状态由 INITIALIZED 变更为 RUNNABLE
// 这打破了 java_start 中 while(get_state() == INITIALIZED) 的死循环
if (osthread->get_state() == INITIALIZED) {
osthread->set_state(RUNNABLE);
// 唤醒在 startThread_lock 上等待的子线程
osthread->startThread_lock()->notify_all();
}
}
}
3. 回调 Java 层 run() 方法:thread.cpp
被唤醒的子线程从 java_start 继续向下执行,进入 thread->run() -> JavaThread::run()。
void JavaThread::run() {
// 初始化 TLAB (Thread Local Allocation Buffer),为 Java 对象分配内存做准备
this->initialize_tlab();
// 设置安全点(Safepoint)相关的线程状态
this->set_thread_state(_thread_in_vm);
// 进入核心的内部执行器
thread_main_inner();
}
void JavaThread::thread_main_inner() {
if (!this->has_pending_exception() && _entry_point != NULL) {
// 创建一个资源上下文
ResourceMark rm(this);
HandleMark hm(this);
// 执行 _entry_point。这个 entry_point 在 jvm.cpp 中指向的是 thread_entry
// thread_entry 会通过 JavaCalls::call_virtual 调用 Java 类中的 Thread.run() 方法
_entry_point(this, this);
}
// 线程执行完毕后的清理工作(如退出 Safepoint,移出线程组,销毁 JavaThread)
this->exit(false);
delete this;
}
六、 Linux 内核与系统调用底层
为了形成闭环,必须理解 pthread_create 在 Linux 系统层面的真实动作。
在 glibc 源代码中,pthread_create 最终会填充一个包含了各种控制标志的结构体,并调用封装好的宏来执行内核系统调用。其本质是调用了 clone()。
Linux 内核中的 clone 标志位分析
在 Linux 2.6 及以后的 NPTL(Native POSIX Thread Library)架构下,线程和进程在内核中都由 task_struct 结构体表示。创建线程时传递给 clone 系统调用的关键参数如下:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);
其底层传递的 flags 掩码通常包含:
| 标志位 (Flags) | 作用说明 | 为什么线程需要 |
|---|---|---|
CLONE_VM |
共享进程虚拟内存空间 | 保证所有 Java 线程能够看到同一个堆(Heap)和方法区。 |
CLONE_FS |
共享文件系统信息 | 共享当前工作目录(cwd)以及根目录、umask 权限。 |
CLONE_FILES |
共享文件描述符表(FD Table) | 任何一个线程打开的 Socket 或 File,其他线程都能直接读写。 |
CLONE_SIGHAND |
共享信号处理函数表 | JVM 注册的信号处理器(如挂起、内存屏障)能对所有线程生效。 |
CLONE_THREAD |
放入相同的线程组(TGID) | 使得新创建的 LWP 在系统层面的 getpid() 返回的是同一个进程ID。 |
总结:全链路图表
整个过程可以通过一个高效的映射表来闭环复盘:
| 阶段 | 所在源文件 / 库 | 关键函数 | 核心职责 |
|---|---|---|---|
| 1. 触发 | Thread.java |
start() -> start0() |
用户入口,面向 Java 的状态检查 |
| 2. 桥接 | Thread.c |
JVM_StartThread |
JNI 动态绑定映射 |
| 3. 分配 | jvm.cpp |
new JavaThread() |
分配 JVM 层 C++ 线程对象与初始化校验 |
| 4. 派发 | thread.cpp |
os::create_thread() |
屏蔽操作系统差异,向 OS 适配层派发请求 |
| 5. 系统调用 | os_linux.cpp |
pthread_create() |
分配 OSThread,调用 POSIX 线程库 |
| 6. 内核创建 | Glibc / Kernel |
clone(CLONE_VM | ...) |
内核真正创建 LWP 并分配独立的 task_struct |
| 7. 握手 | os_linux.cpp |
java_start() |
子线程上线,通过 Monitor 挂起,等待父线程信号 |
| 8. 回调 | thread.cpp / jvm.cpp |
JavaCalls::call_virtual |
握手解锁后,反向回调 Java 对象的 run() 方法 |
这套一比一映射的模型保证了 Java 线程能够享受到 Linux 内核带来的原生调度优化(如 CFS 调度器),但也意味着在 Java 中频繁创建和销毁线程的系统调用开销极高。这也就是为什么在企业级高并发系统工程师的武器库中,线程池(ThreadPoolExecutor) 永远是标配的原因。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)