闲聊 Java 源码中的线程创建
在上文《闲聊 Java 线程生命周期》中我们看到 Thread.start()
最终调用 start0
,另一方面我们也知道 start()
最终调用的是 run()
方法执行的,具体调用过程肯定是在 start0
中了,本篇我们基于 openJDK 21 看看 start0
具体做了些什么事情。
整篇文章细节部分较多,大家也可以直接看最后的总结部分,相对会精简很多。
1 注册绑定
通过搜索源码,可知 start0
第一次出现的位置在 jdk21/src/java.base/share/native/libjava/Thread.c 的一个方法映射表中,start0
映射(或者说绑定)的是一个 JVM_StartThread
,当执行一个start0
时,在底层实际会执行 JVM_StartThread
方法,所以想了解 start0
去看JVM_StartThread
就可以了。
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield0", "()V", (void *)&JVM_Yield},
{"sleep0", "(J)V", (void *)&JVM_Sleep},
{"currentCarrierThread", "()" THD, (void *)&JVM_CurrentCarrierThread},
{"currentThread", "()" THD, (void *)&JVM_CurrentThread},
{"setCurrentThread", "(" THD ")V", (void *)&JVM_SetCurrentThread},
{"interrupt0", "()V", (void *)&JVM_Interrupt},
{"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
{"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
//...
};
2
3
4
5
6
7
8
9
10
11
12
13
如何映射的呢?回到 JDK 代码中看 Thread
类,可见其有个静态方法 registerNatives()
,类中的 native 方法通过该方法在类的初始化阶段便被注册了。
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
// ...
}
2
3
4
5
6
7
8
如果这时候你还没关闭之前的 Thread.c 文件,可以在下面看到这样一个方法:
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
2
3
4
以Java开头,包名、类名、方法名以下划线 _
分隔,最终形成Java_包名_完整类名_方法名
这种命名格式的方法名,这就是JVM中对实现一个本地方法名的相应规定,Java中提供了与其他语言通信的API即 JNI(Java Native Interface),如果想用 Java 调用其他语言(如上面的C)就必须遵守 JNI 中的 API 约定。再比如:
- JNI 接口指针是本地方法的第一个参数。JNI 接口指针的类型是 JNIEnv。总是使用接口指针
env
来操作 Java 对象。 - 第二个参数根据本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对象的引用。静态本地方法的第二个参数是对其 Java 类的引用。
更多内容可见《Java Native Interface Specification Contents》,我们这里不再展开。
2 运行
JVM_StartThread
方法在 jdk21/src/hotspot/share/prims/jvm.cpp,新版的代码比之前长,根据是否涉及 CDS 分为了两个 if 分支。
2.1.1 涉及 CDS 时
这部分不是我们关注的重点,只简单说一下,可以直接跳过。
#if INCLUDE_CDS
if (DumpSharedSpaces) {
if (log_is_enabled(Info, cds)) {
ResourceMark rm;
oop t = JNIHandles::resolve_non_null(jthread);
log_info(cds)("JVM_StartThread() ignored: %s", t->klass()->external_name());
}
return;
}
2
3
4
5
6
7
8
9
在使用 java -Xshare:dump
构建 CDS 时,将忽略多线程的并发执行,因为这期间如果运行多线程并行,会导致符号( symbols )和类( classes )随机顺序加载从而得到的 CDS 存档( archive )存在不确定性( non-deterministic )。
执行 java -Xshare:dump
时最重要的是只运行Java主线程( main Java thread )中创建模块图( module graph )等的代码,此时并未启动主线程。
说了这么多,但 CDS 是什么呢?它的全称是 Class data sharing ,主要是用来在不同的JVM中共享Class-Data信息,通过使用提前生成好的 jsa 归档文件来启动,减少了class加载的步骤,也就减少了启动时间,从而提升应用程序的启动速度。同时加载到内存中的区域多个JVM可以共享,从而减少了内存占用。
早在 JDK 1.5 便引入了 CDS 的概念,当时是将把 rt.jar 这些中的核心类提前转化成内部表示,并转储到一个称为共享存档(shared archive)的文件中,之后可以直接载入内存。默认CDS存档位于 jdk的/server/classes.jsa
。JDK 10 转为开源,OpenJDK 13 / JDK 13 中实现了动态 CDS 归档,提高应用程序类数据共享(AppCDS,Application Class-Data Sharing)的可用性。
2.1.2 不涉及CDS时
#endif
部分代码过长,大家可以从上面 jvm.cpp 处的链接查看,我们选取重点来说,就不再贴全部的源码了。
MutexLocker mu(Threads_lock);
:Threads_lock上锁,保证C++的线程对象和操作系统原生线程不会被清除。当前方法执行完,也就是栈帧释放时,会释放这里的锁 。
在《闲聊 Java 线程生命周期》那里我们知道 start()
方法中有个防止同一个线程多次启动的限制。在 JVM 中还有一个底层限制,创建线程对象(设置 JavaThread )和更新线程状态这两个操作是非原子的,操作本身存在窗口期,所以会通过if再次判断,从而保证线程不可重复创建。
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != nullptr) {
throw_illegal_thread_state = true;
}
// ...
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
2
3
4
5
6
7
Java 中的 Thread 对象,在 JVM 中是对应的 JavaThread 对象。所以最重要的语句之一就是下面的创建语句:
native_thread = new JavaThread(&thread_entry, sz);
sz 是在上面计算得到的线程栈大小,因为使用无符号的线程栈大小,所以不会有负数。
&thread_entry
是传入的运行地址,启动线程,需要一个入口执行点,&thread_entry
这个函数地址便是入口执行点。
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,
obj,
vmClasses::Thread_klass(),
vmSymbols::run_method_name(),
vmSymbols::void_method_signature(),
THREAD);
}
2
3
4
5
6
7
8
9
10
11
thread_entry 中的 JavaCalls::call_virtual
是由 JVM 调用 Java 相关方法的函数,这里 vmSymbols::run_method_name()
代表的就是 Java 的 run 方法,在src/hotspot/share/classfile/vmSymbols.hpp 中可找到run_method_name
对应关系:
template(run_method_name, "run")
JavaCalls::call_virtual
的底层是 os::os_exception_wrapper(call_helper, result, method, args, THREAD)
。os_exception_wrapper
基本上是一个钩子,用于在 Win32 等系统上使用结构化异常处理(线程本地异常过滤器),在 Linux 上不起任何作用,因为 Linux中根本没有做关于异常的相关处理:
void os::os_exception_wrapper(java_call_t f, JavaValue* value, const methodHandle& method,
JavaCallArguments* args, JavaThread* thread) {
f(value, method, args, thread);
}
2
3
4
JavaThread 会创建对应的操作系统线程,具体的我们在后面看。创建关联之后可能由于内存不足不能为 JavaThread 创建 osthread,所以先做判断,如果 osthread
不为空才会通过 prepare
函数将 Java的线程对象 jthread 与 虚拟机 中的 JavaThread 对象关联。
// 不为空说明内存够用,开始通过prepare函数将Java线程对象与 JavaThread 对象关联
if (native_thread->osthread() != nullptr) {
// Note: the current thread is not being used within "prepare".
native_thread->prepare(jthread);
}
// ...
// 如果为空,会释放相关资源,清理当前线程,并报出 OOM 异常。
if (native_thread->osthread() == nullptr) {
ResourceMark rm(thread);
log_warning(os, thread)("Failed to start the native thread for java.lang.Thread \"%s\"",
JavaThread::name_for(JNIHandles::resolve_non_null(jthread)));
// No one should hold a reference to the 'native_thread'.
native_thread->smr_delete();
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
os::native_thread_creation_failed_msg());
}
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
os::native_thread_creation_failed_msg());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
之后通过 JFR_ONLY(Jfr::on_java_thread_start(thread, native_thread);)
由调用线程确保被调用线程(即新建的线程)拥有有效的 _vm_thread_id 和 _contextual_id。
The starter thread ensures that the startee has a valid _vm_thread_id and _contextual_id.
This is to avoid recursion in thread assignment since accessing the java threadObj can lead to events being fired, a situation the starter thread can handle but not the startee.
最后通过 Thread::start(native_thread);
启动线程,实现对 Java 线程run()
方法的调用。
3 运行中的关键函数
上面是一个大概的执行过程,我们再进入一些关键的函数中看看做了些什么。
3.1 new JavaThread
JVM 的 JavaThread 对象创建具体代码,内部会通过 os::create_thread(this, thr_type, stack_sz);
调用操作系统线程接口创建对应的操作系统线程。
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : JavaThread() {
_jni_attach_state = _not_attaching_via_jni;
// 设置入口,入口即上面的 &thread_entry,用于后面 JavaThread::run 函数的执行。
set_entry_point(entry_point);
// Create the native thread itself.
// %note runtime_23
os::ThreadType thr_type = os::java_thread;
thr_type = entry_point == &CompilerThread::thread_entry ? os::compiler_thread : os::java_thread;
os::create_thread(this, thr_type, stack_sz);
}
2
3
4
5
6
7
8
9
10
3.2 os::create_thread
各系统关于 os::create_thread
的具体实现可见 src/hotspot/os 包,我们这里看下常用的 Linux 版 ,源码太长,涉及各种属性的填充与判断,我们摘取重要部分来看。
最开始时线程状态是 ALLOCATED :
// Initial state is ALLOCATED but not INITIALIZED
osthread->set_state(ALLOCATED);
2
其中最关键的就是调用POSIX Pthreads 线程库的 pthread_create
(内部使用 clone 系统调用实现)创建线程一段。
// ...
int ret = 0;
int limit = 3;
do {
ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);
} while (ret == EAGAIN && limit-- > 0);
// ...
2
3
4
5
6
7
在下面还有一步等待子线程执行完成的过程,其实就是在等待 pthread_create
创建的线程状态变更为 INITIALIZED 或者因资源不足创建失败。
// Wait until child thread is either initialized or aborted
{
Monitor* sync_with_child = osthread->startThread_lock();
MutexLocker ml(sync_with_child, Mutex::_no_safepoint_check_flag);
while ((state = osthread->get_state()) == ALLOCATED) {
sync_with_child->wait_without_safepoint_check();
}
}
2
3
4
5
6
7
8
3.2.1 thread_native_entry
这段代码中还有个重要方法就是 thread_native_entry
,这是所有新建线程的启动例程,其实就是 pthread_create
创建的新线程会从 thread_native_entry
这个函数开始执行。
// ...
// 配置 线程本地存储 ThreadLocalStorage ,内部使用 ThreadLocalStorage::set_thread(this);
thread->initialize_thread_current();
// ...
// handshaking with parent thread
{
// 互斥锁
MutexLocker ml(sync, Mutex::_no_safepoint_check_flag);
// notify parent thread
// 设置当前线程状态为 INITIALIZED 初始化完成
osthread->set_state(INITIALIZED);
// 唤醒父级线程,让 os::create_thread 可以正常返回。
sync->notify_all();
// wait until os::start_thread()
// 初始化后循环等待,直到调用 os::start_thread() 变更线程状态且被唤醒。
while (osthread->get_state() == INITIALIZED) {
sync->wait_without_safepoint_check();
}
}
// ...
// call one more level start routine
thread->call_run();
// ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
被唤醒后执行 thread->call_run()
,内部通过 this->run()
进入 thread_main_inner()
,最后在 thread_main_inner
中调用 this->entry_point()(this, this)
通过
JavaCalls::call_virtual(&result,
obj,
vmClasses::Thread_klass(),
vmSymbols::run_method_name(),
vmSymbols::void_method_signature(),
THREAD);
2
3
4
5
6
执行 Java 中的 run
方法完成最终的线程运行。
3.3 JavaThread::prepare
prepare
函数 将 Java的线程对象 jthread 与 虚拟机 中的 JavaThread 对象关联。
void JavaThread::prepare(jobject jni_thread, ThreadPriority prio) {
// 检测是否持有锁,只有持有 Threads_lock 才能进行下一步
assert(Threads_lock->owner() == Thread::current(), "must have threads lock");
assert(NoPriority <= prio && prio <= MaxPriority, "sanity check");
// 包装当前Java线程对象,将其从 JNI 句柄中获取为 C++ 层的对象并放入一个新句柄thread_oop 中, 之后该句柄可以将转为C++的线程对象传递给其他方法。
Handle thread_oop(Thread::current(),
JNIHandles::resolve_non_null(jni_thread));
assert(InstanceKlass::cast(thread_oop->klass())->is_linked(),
"must be initialized");
// 借助 thread_oop 句柄将Java层的线程属性等信息设置成C++层 JavaThread 的属性等信息
set_threadOopHandles(thread_oop());
// 没有优先级时,设置优先级
if (prio == NoPriority) {
prio = java_lang_Thread::priority(thread_oop());
assert(prio != NoPriority, "A valid priority should be present");
}
// 将 Java 优先级下推至本地线程,该过程需要 Threads_lock
Thread::set_priority(this, prio);
// 将当前新增的 JavaThread 类放入到全局线程列表中
Threads::add(this);
// 释放 Threads_lock 锁等
// 因为 JavaThread* 已经通过 ThreadsList 对 JVM/TI 可见,所以不想等待 Theads_lock 在调用者中的某个地方被释放。
java_lang_Thread::release_set_thread(thread_oop(), this);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3.4 Thread::start
该方法在src/hotspot/share/runtime/thread.cpp中,主要作用是调用 set_thread_status
将线程状态变更为 RUNNABLE,同时调用 os::start_thread
启动最终线程。
void Thread::start(Thread* thread) {
if (thread->is_Java_thread()) {
// 设置状态为 RUNNABLE
java_lang_Thread::set_thread_status(JavaThread::cast(thread)->threadObj(),JavaThreadStatus::RUNNABLE);
}
os::start_thread(thread);
}
2
3
4
5
6
7
8
3.4.1 os::start_thread
这段代码在src/hotspot/share/runtime/os.cpp比较简单,就是先设置操作系统线程状态为 RUNNABLE,同时调用对应的 pd_start_thread
实现最终的调用。
void os::start_thread(Thread* thread) {
OSThread* osthread = thread->osthread();
osthread->set_state(RUNNABLE);
pd_start_thread(thread);
}
2
3
4
5
在 Java 线程上,当 start_thread 作为 Thread.start 的结果被调用时,Java 线程对象上的操作是同步的。 因此,不会出现竞相启动线程的情况,也就不会出现我们在处理线程时线程退出的情况。
最后调用 pd_start_thread
唤醒 2.3.1 中的等待,执行之后的 run 方法。
void os::pd_start_thread(Thread* thread) {
OSThread * osthread = thread->osthread();
assert(osthread->get_state() != INITIALIZED, "just checking");
// 获得线程的 Monitor 对象,其加锁过程在上面的 os::create_thread 中
Monitor* sync_with_child = osthread->startThread_lock();
// 通过 MutexLocker 获取互斥锁
MutexLocker ml(sync_with_child, Mutex::_no_safepoint_check_flag);
// 唤醒 上面 2.3.1 中阻塞的线程,执行之后的 run 方法。
sync_with_child->notify();
}
2
3
4
5
6
7
8
9
10
4 总结
最后我们再借助时序图梳理一下整个过程。
图中黑色箭头部分基本都是创建操作系统线程的部分:
- 当执行
Thread.start()
后通过 JNI 执行本地方法start0()
对应的JVM层代码JVM_StartThread
。 JVM_StartThread
通过new JavaThread
创建 JVM 层的 JavaThread 对象,。- 在 JavaThread 中通过
set_entry_point
绑定Java线程的入口方法thread_entry
,后面会通过这个方法调用 Java 代码中的run()
方法。 - 通过
os::create_thread
设置线程状态为ALLOCATED
,调用Linux线程库的 pthread_create 方法从而调用Linux的 clone 系统调用创建一个对应的操作系统线程。同时父线程循环等待,直到新建线程初始化完成(即状态变为 INITIALIZED)或中止(父等待新线程创建这一条未在时序图中展示)。 pthread_create
创建的新线程会从thread_native_entry
这个函数开始执行 ,会先将线程状态变更为 INITIALIZED,调用notify_all();
唤醒父线程,然后内部循环等待,直到后面调用os::start_thread()
唤醒后执行最后的run
方法。
- 在 JavaThread 中通过
橘黄色箭头部分是将Java线程对象与 JVM 层JavaThread对象关联/绑定的部分:
JVM_StartThread
创建 JVM 层的 JavaThread 对象之后,通过JavaThread::prepare
将 Java的线程对象 jthread 与 虚拟机 中的 JavaThread 对象关联,并将当前新增的 JavaThread 类放入到全局线程列表 ThreadsList 中 。
JVM_StartThread
经过上面黑色箭头部分和黄色箭头部分的这两个步骤,将 Java 层的线程对象与 JVM 层的线程对象以及操作系统的线程对象完成了最终的关联绑定。
蓝色箭头部分是 JVM 通过 Thread::start(native_thread)
执行线程启动部分:
JVM_StartThread
完成线程对象的绑定后,确保资源满足执行后,执行Thread::start(native_thread)
开始启动线程。Thread::start(native_thread)
会先将Java线程状态改成 RUNNABLE,之后执行os::start_thread
,而这个方法会将操作系统线程对象的状态设置成 RUNNABLE,最终通过调用notify()
唤醒之前thread_native_entry
中等待的线程。
红色箭头部分是JVM层 notify
通知后,唤醒操作系统线程继续执行 run()
方法的部分。
- 当在
thread_native_entry
中等待的线程收到蓝色箭头部分的通知后,判断当前满足继续执行的条件,开始执行thread->call_run();
。 thread->call_run();
会执行JavaThread::run
(在其前后,还有JavaThread::pre_run()
和JavaThread::post_run()
我们这里就不细说了 ),里面会执行JavaThread::thread_main_inner
。JavaThread::thread_main_inner
会通过this->entry_point()(this, this);
调用最初的thread_entry
函数。thread_entry
函数通过JavaCalls::call_virtual
函数调用 Java 代码中的run()
方法。
除特别注明外,本站所有文章均为 windcoder 原创,转载请注明出处来自: xianliao-java-yuanmazhongdexianchengchuangjian

这篇文章写得深入浅出,让我这个小白也看懂了!