周末抽了点时间, 研究了下 HotSpot 是如何创建 Java 线程的, 顺便总结一下文中引用的源码里删除很多细节, 只保留了主要过程, 感兴趣的同学可以自己去深入研究能力有限, 错误地方恳请指正
一: Java 线程介绍
做 Java 开发或者 Android 的同学, 肯定对 Java 线程很熟悉之前在阅读一些 JVM 相关的书时, 了解到 Java 线程其实是映射到操作系统的内核线程上的, 所以 Java 线程基本上也就是操作系统在进行管理在 Linux 中, 线程和进程用的是同一个结构体进行描述的, 只不过进程拥有自己独立的地址空间, 而同一个进程的线程之间是共享资源的
二: Java 线程入口分析
想要启动一个 Java 线程, 主要有两种方法, 第一种可以实现一个继承自 Thread 的子类, 重写 run(); 第二种可以实现一个 Runnable, 交给 Thread 执行这两种方法都很简便, 我们可以根据自己的业务需求选择, 但是无论选择哪种方法, 都需要调用 Thread.start(), 才能真正的启动一个异步线程进行工作, 笔者刚上大学接触 Java 时, 曾经天真的以为调用 Thread.run() 就行了, 其实直接调用 run(),JVM 并不会去创建一个线程, run() 只会工作在原有线程, 和调用普通对象的方法没有任何区别
上面提到了要想启动线程, 必须要调用 Thread.start(), 那么这个方法便是我们研究的入口了
- public synchronized void start() {
- if (threadStatus != 0)
- throw new IllegalThreadStateException();
- group.add(this);
- boolean started = false;
- try {
- start0();
- started = true;
- } finally {
- try {
- if (!started) {
- group.threadStartFailed(this);
- }
- } catch (Throwable ignore) {
- /* do nothing. If start0 threw a Throwable then
- it will be passed up the call stack */
- }
- }
- }
- private native void start0();
通过上面的代码可以得知, start() 方法中做了一些线程状态判断等工作, 但是真正启动 Java 线程的地方是调用了 start0(),start0() 是一个 Native 方法 start0() 是何处实现的呢? 我们先来看看 Thread.c 中的一段定义:
- static JNINativeMethod methods[] = {
- {"start0", "()V", (void *)&JVM_StartThread},
- {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
- {"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
- {"suspend0", "()V", (void *)&JVM_SuspendThread},
- {"resume0", "()V", (void *)&JVM_ResumeThread},
- {"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
- {"yield", "()V", (void *)&JVM_Yield},
- {"sleep", "(J)V", (void *)&JVM_Sleep},
- {"currentThread", "()" THD, (void *)&JVM_CurrentThread},
- {"countStackFrames", "()I", (void *)&JVM_CountStackFrames},
- {"interrupt0", "()V", (void *)&JVM_Interrupt},
- {"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted},
- {"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
- {"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
- {"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
- {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
- };
上面定义了一个数组, 数组中存放的为 JNINativeMethod 类型的结构体变量, JNINativeMethod 定义在 jni.h 中:
- typedef struct {
- char *name;
- char *signature;
- void *fnPtr;
- } JNINativeMethod;
JNINativeMethod 主要是进行一个 jni 方法的映射关系, 将 native 方法和真正的实现方法进行绑定那么具体是何时进行绑定的呢? java 层的 Thread 在类的静态初始化块中, 调用了 registerNatives() 方法:
- private static native void registerNatives();
- static {
- registerNatives();
- }
我们来看看 registerNatives() 对应的 Jni 方法:
- JNIEXPORT void JNICALL Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
- {
- (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
- }
可见此时会将上述数组 methods 中的方法映射关系进行注册
注册好了后, 我们再来看看上面定义的数组, 这里面将 Java 线程中的很多 native 方法与实现其功能的方法指针进行了绑定, 比如 start0() 就和 JVM_StartThread 进行了绑定所以我们想研究 start0(), 就直接看看 JVM_StartThread 指针所指向的方法即可
三: Java 线程的创建
JVM_StartThread 定义在 jvm.cpp 中:
- JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
- JVMWrapper("JVM_StartThread");
- JavaThread *native_thread = NULL;
- bool throw_illegal_thread_state = false;
- {
- MutexLocker mu(Threads_lock);
- if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
- throw_illegal_thread_state = true;
- } else {
- jlong size =
- java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
- size_t sz = size > 0 ? (size_t) size : 0;
- native_thread = new JavaThread(&thread_entry, sz);
- if (native_thread->osthread() != NULL) {
- native_thread->prepare(jthread);
- }
- }
- }
- ......
- Thread::start(native_thread);
- JVM_END
源码中本来有很多英文注释, 我这里先给删了, 下面来一步步分析下上面的代码吧
1: 判断 Java 线程是否已经启动, 如果已经启动过, 则会抛异常
- if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
- throw_illegal_thread_state = true;
- }
2: 如果第一步判断中, Java 线程没有启动过, 则会开始创建 Java 线程
- jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
- size_t sz = size > 0 ? (size_t) size : 0;
- native_thread = new JavaThread(&thread_entry, sz);
Java 线程的创建过程主要就在 JavaThread 的构造函数中:
- JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
- Thread()
- {
- initialize();
- _jni_attach_state = _not_attaching_via_jni;
- set_entry_point(entry_point);
- os::ThreadType thr_type = os::java_thread;
- thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
- os::java_thread;
- os::create_thread(this, thr_type, stack_sz);
- }
最后一句 os::create_thread(this, thr_type, stack_sz) 便开始真正的创建 Java 线程对应的内核线程
- bool os::create_thread(Thread* thread, ThreadType thr_type,
- size_t req_stack_size) {
- ......
- pthread_t tid;
- int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);
- ......
- return true;
- }
上面这个方法主要就是利用 pthread_create() 来创建线程其中第三个参数 thread_native_entry 便是新起的线程运行的初始地址, 其为定义在 os_bsd.cpp 中的一个方法指针, 而第四个参数 thread 即 thread_native_entry 的参数:
- static void *thread_native_entry(Thread *thread) {
- ......
- thread->run();
- ......
- return 0;
- }
新线程创建后就会从 thread_native_entry() 开始运行, thread_native_entry() 中调用了 thread->run():
- // thread.cpp
- void JavaThread::run() {
- ......
- thread_main_inner();
- }
此方法最后调用了 thread_main_inner():
- // thread.cpp
- void JavaThread::thread_main_inner() {
- if (!this->has_pending_exception() &&
- !java_lang_Thread::is_stillborn(this->threadObj())) {
- {
- ResourceMark rm(this);
- this->set_native_thread_name(this->get_thread_name());
- }
- HandleMark hm(this);
- this->entry_point()(this, this);
- }
- DTRACE_THREAD_PROBE(stop, this);
- this->exit(false);
- delete this;
- }
我们重点关注下 this->entry_point()(this, this),entry_point() 返回的其实就是在 new JavaThread(&thread_entry, sz) 时传入的 thread_entry 这里就相当于调用了 thread_entry(this,this)thread_entry 定义在 jvm.cpp 中:
- // jvm.cpp
- static void thread_entry(JavaThread* thread, TRAPS) {
- HandleMark hm(THREAD);
- Handle obj(THREAD, thread->threadObj());
- JavaValue result(T_VOID);
- JavaCalls::call_virtual(&result,
- obj,
- KlassHandle(THREAD, SystemDictionary::Thread_klass()),
- vmSymbols::run_method_name(),
- vmSymbols::void_method_signature(),
- THREAD);
- }
哈哈, 这里见到了一个老朋友: JavaCallsJavaCalls 模块是用来调用 Java 方法的不了解的朋友可以直接看之前写的文章 JVM 方法执行的来龙去脉
我们来看看这里调用 JavaCalls::call_virtual() 传入的一些参数:
obj:Java 线程对象;
KlassHandle(THREAD, SystemDictionary::Thread_klass()):Java 线程类, 记录在 SystemDictionary 中, 即 java_lang_Thread;
vmSymbols::run_method_name(): 即 "run";
vmSymbols::void_method_signature(): 即 "()V";
经过上面的分析, 这里其实就是开始调用 Java 线程对象的 run() 方法
3: 开始执行所创建的内核线程, 即从第二步所说的 thread_entry 处开始执行
Thread::start(native_thread);
四: 总结
到这里 Java 线程就已经真正的运行起来了, 总结下上面的过程:
1: 调用 Java 线程 start() 方法, 通过 jni 方式, 调用到 JVM 层
2:JVM 通过 pthread_create() 创建一个系统内核线程, 并指定内核线程的初始运行地址, 即一个方法指针
3: 在内核线程的初始运行方法中, 利用 JavaCalls 模块, 调用 java 线程的 run() 方法, 开始 java 级别的线程执行
来源: https://juejin.im/entry/5aa3d4a86fb9a028b6172260