JVM之Java線程啟動流程

歡迎關(guān)注我的專欄:半棧工程師

周末抽了點時間,研究了下HotSpot是如何創(chuàng)建Java線程的,順便總結(jié)一下。文中引用的源碼里刪除很多細(xì)節(jié),只保留了主要過程,感興趣的同學(xué)可以自己去深入研究。能力有限,錯誤地方懇請指正。

一:Java線程介紹

做Java開發(fā)或者Android的同學(xué),肯定對Java線程很熟悉。之前在閱讀一些JVM相關(guān)的書時,了解到Java線程其實是映射到操作系統(tǒng)的內(nèi)核線程上的,所以Java線程基本上也就是操作系統(tǒng)在進行管理。在Linux中,線程和進程用的是同一個結(jié)構(gòu)體進行描述的,只不過進程擁有自己獨立的地址空間,而同一個進程的線程之間是共享資源的。

二:Java線程入口分析

想要啟動一個Java線程,主要有兩種方法,第一種可以實現(xiàn)一個繼承自Thread的子類,重寫run();第二種可以實現(xiàn)一個Runnable,交給Thread執(zhí)行。這兩種方法都很簡便,我們可以根據(jù)自己的業(yè)務(wù)需求選擇,但是無論選擇哪種方法,都需要調(diào)用Thread.start(),才能真正的啟動一個異步線程進行工作,筆者剛上大學(xué)接觸Java時,曾經(jīng)天真的以為調(diào)用Thread.run()就行了,其實直接調(diào)用run(),JVM并不會去創(chuàng)建一個線程,run()只會工作在原有線程,和調(diào)用普通對象的方法沒有任何區(qū)別。

上面提到了要想啟動線程,必須要調(diào)用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()方法中做了一些線程狀態(tài)判斷等工作,但是真正啟動Java線程的地方是調(diào)用了start0(),start0()是一個Native方法。start0()是何處實現(xiàn)的呢?我們先來看看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},
};

上面定義了一個數(shù)組,數(shù)組中存放的為JNINativeMethod類型的結(jié)構(gòu)體變量,JNINativeMethod定義在jni.h中:

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

JNINativeMethod主要是進行一個jni方法的映射關(guān)系,將native方法和真正的實現(xiàn)方法進行綁定。那么具體是何時進行綁定的呢?java層的Thread在類的靜態(tài)初始化塊中,調(diào)用了registerNatives()方法:

   private static native void registerNatives();
    static {
        registerNatives();
    }

我們來看看registerNatives()對應(yīng)的Jni方法:

JNIEXPORT void JNICALL Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

可見此時會將上述數(shù)組methods中的方法映射關(guān)系進行注冊。

注冊好了后,我們再來看看上面定義的數(shù)組,這里面將Java線程中的很多native方法與實現(xiàn)其功能的方法指針進行了綁定,比如start0()就和JVM_StartThread進行了綁定。所以我們想研究start0(),就直接看看JVM_StartThread指針?biāo)赶虻姆椒纯伞?/p>

三:Java線程的創(chuàng)建

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線程是否已經(jīng)啟動,如果已經(jīng)啟動過,則會拋異常。

if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
 }

2: 如果第一步判斷中,Java線程沒有啟動過,則會開始創(chuàng)建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線程的創(chuàng)建過程主要就在JavaThread的構(gòu)造函數(shù)中:

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) 便開始真正的創(chuàng)建Java線程對應(yīng)的內(nèi)核線程。

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()來創(chuàng)建線程。其中第三個參數(shù)thread_native_entry便是新起的線程運行的初始地址,其為定義在os_bsd.cpp中的一個方法指針,而第四個參數(shù)thread即thread_native_entry的參數(shù):

static void *thread_native_entry(Thread *thread) {
  ......
  thread->run();
  ......
  return 0;
}

新線程創(chuàng)建后就會從thread_native_entry()開始運行,thread_native_entry()中調(diào)用了thread->run():

// thread.cpp
void JavaThread::run() {
  ......
  thread_main_inner();
}

此方法最后調(diào)用了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;
}

我們重點關(guān)注下this->entry_point()(this, this),entry_point()返回的其實就是在 new JavaThread(&thread_entry, sz) 時傳入的thread_entry。這里就相當(dāng)于調(diào)用了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);
}

哈哈,這里見到了一個老朋友:JavaCalls。JavaCalls模塊是用來調(diào)用Java方法的。不了解的朋友可以直接看之前寫的文章《JVM方法執(zhí)行的來龍去脈》。

我們來看看這里調(diào)用JavaCalls::call_virtual()傳入的一些參數(shù):

obj:Java線程對象;

KlassHandle(THREAD, SystemDictionary::Thread_klass()):Java線程類,記錄在SystemDictionary中,即java_lang_Thread;

vmSymbols::run_method_name():即"run";

vmSymbols::void_method_signature():即"()V";

經(jīng)過上面的分析,這里其實就是開始調(diào)用Java線程對象的run()方法。

3:開始執(zhí)行所創(chuàng)建的內(nèi)核線程,即從第二步所說的thread_entry處開始執(zhí)行。

Thread::start(native_thread);

四:總結(jié)

到這里Java線程就已經(jīng)真正的運行起來了,總結(jié)下上面的過程:

1:調(diào)用Java線程start()方法,通過jni方式,調(diào)用到JVM層。

2:JVM通過pthread_create()創(chuàng)建一個系統(tǒng)內(nèi)核線程,并指定內(nèi)核線程的初始運行地址,即一個方法指針。

3:在內(nèi)核線程的初始運行方法中,利用JavaCalls模塊,調(diào)用java線程的run()方法,開始java級別的線程執(zhí)行。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容