JVM之Java线程启动流程

欢迎关注我的专栏:半栈工程师

周末抽了点时间,研究了下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);
}

哈哈,这里见到了一个老朋友:JavaCalls。JavaCalls模块是用来调用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级别的线程执行。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容