JVM线程源码浅析-JVM线程如何映射到操作系统线程

创建一个Java线程的路径:

image.png

我们模糊掉内部的控制逻辑,首先聚焦于线程创建的主流程:

创建不同层级的线程对象(由Java语言层向下传递)

创建Java语言层面的Thread对象,调用start()方法后;会调用JVM的JVM_StartThread()方法创建JavaThread对象,并按照当前虚拟机所处的操作系统创建不同的OSThread对象(操作系统核心级线程);

不同层级的线程依次启动(从OS层开始向上传递)

核心级线程创建成功后,会调用入口函数:thread_native_entry方法,该方法会调用JavaThread::run方法启动JVM线程,JVM线程启动后会调用Thread对象传入的thread_entry方法,该方法会调用Thread::run方法-即我们代码中定义的该线程需要执行的代码块;

由Java语言层向下传递

Java语言层

在Java语言中,我们手动创建线程时,会通过类似下面的方式启动:

// 定义Thread对象(提供线程run()方法的实现)
Thread runnableThread = new Thread(() -> {
    System.out.println("[runnableThread] begin start");
    System.out.println("[runnableThread] end start");
});

// 启动定义的Thread (如果我们手动调用Thread.run(),并不会出现上述的日志答打印)
runnableThread.start();
复制代码

我们进入start()方法:

public synchronized void start() {
    /**
     * Thread状态 : 0 -> "NEW"
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

boolean started = false;
    try {
        // native 方法
        start0();
        started = true;
    }
}

private native void start0();
复制代码

start0()方法需要在映射表(native方法 -> JVM方法的映射) 中找到对应的的JVM层的方法,而Thread.java中的static代码块调用 registerNatives()方法初始化了映射表:

// ----------------------------- Java语言层 ---------------------------//
public class Thread implements Runnable {
    // 这个方法对应了JVM中的 Java_java_lang_Thread_registerNatives 方法
    private static native void registerNatives();
    static {
        registerNatives();
    }
}    

// ----------------------------- JVM层 ---------------------------//
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls) {
    // methods即为映射表数组
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

// 映射表 eg. 调用start0时 会通过该映射表映射到 JVM_StartThread 这个JVM方法中
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},
};
复制代码

于是随着start0()对应的JVM_StartThread这个JVM方法被执行,代码的层次也从Java语言层向下传递到了JVM层;

JVM层

JVM_StartThread

JVM_StartThread方法主要是通过Java语言层的Thread对象中传递的信息,创建了JavaThread这个JVM线程对象:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JavaThread *native_thread = NULL;
  // 通过bool量表示是否有异常,因为下面持有 Threads_lock 锁时,抛出异常会导致锁不能被释放
  bool throw_illegal_thread_state = false;
  
  {
    // 获取 Threads_lock 这个JVM内部的 Mutex锁
    MutexLocker mu(Threads_lock);

    // 如果线程已经创建成功,则不可以重复创建
    // PS: 不可以通过Thread状态机判断,因为创建线程成功与修改线程状态这两个操作是非原子的,存在窗口期
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      // 获取Thread中定义的栈大小,一般我们创建Thread对象时不会显式指定传入stackSize对象
      // 未指定时,后面在创建栈时会使用 -Xss 这个JVM参数指定的栈大小
      jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
     
      // 创建JVM线程(用JavaThread对象表示)
      // thread_entry 为 Thread::run
      native_thread = new JavaThread(&thread_entry, sz);
     
      ...

JVM_END
复制代码

构建JavaThread对象

构建JavaThread对象,通过glibc库方法pthread_create创建内核级线程,并将JavaThread(JVM)层线程对象与操作系统内核级线程关联起来;

Linux系统为例,一个内核级线程会对应一个JavaThread(一对一);

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
                       Thread() {
  // entry_point 线程创建成功后的入口代码 (这里是Java语言层面传入的 Thread.run()方法)
  set_entry_point(entry_point);
  // 创建的Thread类型为javaThread
  os::ThreadType thr_type = os::java_thread;

  // 调用不同操作系统的创建线程方法创建线程
  os::create_thread(this, thr_type, stack_sz);
}

/**
 * JVM为每一种操作系统(linux、bsd...)都写了对应的创建线程的OS方法 
 * 下文以Linux为例
 */
bool os::create_thread(Thread* thread, ThreadType thr_type,
                       size_t req_stack_size) {

  // 操作系统线程对象
  OSThread* osthread = new OSThread(NULL, NULL);
  if (osthread == NULL) {
    return false;
  }
  
  // 将JavaThread(JVM)层线程对象与操作系统内核级线程关联起来,
  // 这样就可以通过使用JavaThread操作内核级线程 (一对一的关系)
  thread->set_osthread(osthread);
  {
    // 值-结果变量
    pthread_t tid;
    // 通过pthread_create方法创建内核级线程
    // thread_native_entry 这个是内核级线程的 start_routine(线程创建成功后会调用)
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);

    // 操作系统 tid (轻量级进程Id)
    osthread->set_pthread_id(tid);
  }
  return true;
}
复制代码

OS 层 (通过glibc进行调用)

glibc中的pthread_create方法主要是创建一个OS内核级线程,我们不深入细节,主要是为该线程分配了栈资源;需要注意的是这个栈资源对于JVM而言是堆外内存,因此堆外内存的大小会影响JVM可以创建的线程数。

我们知道对于内核级线程,操作系统会为其创建一套栈:用户栈+内核栈,其中用户栈工作在用户态,内核栈工作在内核态,在发生系统调用时,线程的执行会从用户栈切换到内核栈;

那么这一套栈与JVM规范中定义的JVM栈+本地方法栈有什么联系呢? 在JVM概念中,JVM栈用来执行Java方法,而本地方法栈用来执行native方法;但需要注意的是JVM只是在概念上区分了这两种栈,而并没有规定如何实现,在HotSpot中,则是将JVM栈与本地方法栈二合一,使用核心线程的用户栈来实现(因为JVM栈和本地方法栈都是属于用户态的栈),即Java方法与native方法都在同一个用户栈中调用,而当发生系统调用时,再切换到核心栈运行。

pthread_create : __pthread_create_2_1

int __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
            void *(*start_routine) (void *), void *arg)
{
  STACK_VARIABLES;
  struct pthread *pd = NULL;
  
  // 上游传递的线程属性
  const struct pthread_attr *iattr = (struct pthread_attr *) attr;
  // 为线程分配CPU资源
  cpuset = malloc (cpusetsize);
  // 为线程分配栈资源,分配失败抛出OOM异常-ENOMEM
  int err = ALLOCATE_STACK (iattr, &pd);
  if (__glibc_unlikely (err != 0)) {
      retval = err == ENOMEM ? EAGAIN : err;
      goto out;
  }
  
  // 后续主要为通过当前核心级线程的TCB clone 出一个新的核心级线程的TCB(包括内核栈与用户栈), 这里不再赘述

  return retval;
}
复制代码

thread_native_entry

OS内核级线程创建完成后,会执行thread_native_entry这个线程入口函数进行OS内核级线程的初始化,并开始运行JavaThread

static void *thread_native_entry(Thread *thread) {
  OSThread* osthread = thread->osthread();
  // 此时运行的是通过glibc创建的linux核心级线程,所以当前的线程id为linux线程的id
  osthread->set_thread_id(os::current_thread_id());

  {
    // OS核心级线程初始化完毕
    osthread->set_state(INITIALIZED);
  }

  // 初始化完成JVM对应的操作系统线程后,开始运行JavaThread::run
  thread->run();

  return 0;
}
复制代码

从OS层开始向上传递

OS层 -> JVM层 (通过调用 JavaThread::run 方法)

// The first routine called by a new Java thread
void JavaThread::run() {
  // 初始化TLAB 线程缓冲区(-XX:+UseTLAB)
  this->initialize_tlab();

  // 设置栈、确定栈的生长方向
  this->record_base_of_stack_pointer();
  this->record_stack_base_and_size();

  thread_main_inner();
}

void JavaThread::thread_main_inner() {
    // JavaThread对象中传入的entry_point为Thread对象的Thread::run方法
    this->entry_point()(this, this);
  }
}
复制代码

JVM层 -> Java语言层 (通过调用Thread::run方法)

static void thread_entry(JavaThread* thread, TRAPS) {
  HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  // JavaCalls模块是用来调用Java方法的
  // 这里其实就是开始调用Java线程对象的run()方法
  JavaCalls::call_virtual(&result,
                          obj, // Thread对象
                          SystemDictionary::Thread_klass(), // Java_java_lang_Thread
                          vmSymbols::run_method_name(), // run()
                          vmSymbols::void_method_signature(), // V
                          THREAD);
}
复制代码

总结:

至此,我们分析完了一个线程从Java语态到JVM语态再到OS语态的全过程; 可以看到,对于HotSpot VM而言,其在Linux操作系统上实现的Java线程即对OS内核级线程进行了一对一的映射,所有的线程调度、内存分配都交由操作系统进行管理。

Guess you like

Origin juejin.im/post/7059363106857680933