由start函数浅析Java Thread

发布时间 2023-05-21 22:27:38作者: bppps

由start函数浅析Java Thread

Java的Thread由创建到实际运行在底层都分别对应着不同主机平台上的线程,如Linux使用pthread_create()函数来创建线程、windows平台使用_beginthreadex()函数来创建线程。下面基于java.lang.Thread.java中的start函数的源码对线程创建及启动进行分析(以hotspot虚拟机为例)。

Java 层面

/src/share/classes/java/lang/Thread.java#l143

public class Thread implements Runnable {
    // ...  
    private long eetop; // 对应VM层面的JavaThread
    // ...
    public synchronized void start() {
        // ...
        start0(); // 本地方法
        // ...
    }
}

start()方法调用了本地方法start0(),查JNI得知调用JVM层面的方法。
/src/share/native/java/lang/Thread.c#l44

{"start0",           "()V",        (void *)&JVM_StartThread}

即对应VM层面的JVM_StartThread方法。

VM层面

数据结构

java.lang.Thread.java与Vm层面的JavaThread和OSThread共同实现。
/src/share/vm/runtime/thread.hpp#l103

class Thread: public ThreadShadow {
    // ...
    OSThread* _osthread;  // Platform-specific thread information
    // ...
}

class JavaThread: public Thread {
    // ...
}

VM层面的Thread包含了指向OSThread的指针,OSThread包含了底层平台相关的线程信息,如线程的状态、线程id等。
/src/share/vm/runtime/osThread.hpp#l61

class OSThread: public CHeapObj<mtThread> {
    // ...
private:
    // _thread_id is kernel thread id (similar to LWP id on Solaris). Each thread has a unique thread_id (BsdThreads or NPTL). It can be used to access /proc.
    thread_id_t _thread_id;
}

方法执行

JVM_StartThread

进入JVM_StartThread方法中观察start0()方法执行逻辑。
/src/share/vm/prims/jvm.cpp#l3110

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
    // 忽略部分影响流程逻辑分析代码
    // ... 
    JavaThread *native_thread = NULL;
    // ...
    // 计算线程栈大小
    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);
    // 线程创建完毕之后处于initialized状态,还未进入runnable状态
    // ...
    // 线程开始运行
    Thread::start(native_thread);
JVM_END

这个方法中包含两个关键函数.

  1. JavaThread(ThreadFunction entry_point, size_t stack_size = 0);
  2. Thread::start(Thread* thread);

1中函数包含了两个参数,第一个参数是线程主函数(可粗犷理解为封装了调用run()的函数地址),第二个参数是线程栈的大小,传入第一个参数的是thread_entry
/src/share/vm/prims/jvm.cpp#l3097

static void thread_entry(JavaThread* thread, TRAPS) {
    HandleMark hm(THREAD);
    Handle obj(THREAD, thread->threadObj());
    JavaValue result(T_VOID);
    // call_virtual 即方法调用
    JavaCalls::call_virtual(&result,
                            obj,
                            KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                            vmSymbols::run_method_name(),
                            vmSymbols::void_method_signature(),
                            THREAD);
}

函数体中的vmSymbols::run_method_name()对应着Thread.java中的run方法。
/src/share/vm/classfile/vmSymbols.hpp#l319

template(run_method_name,                           "run")

JavaThread::JavaThread

JavaThread构造方法,主要做的包括保存线程的entry_point,将线程创建信息传递至底层os层面。
/src/share/vm/runtime/thread.cpp#l1570

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()
  // ...
  // 保存entry_point
  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;
  // 根据底层平台调用不同的create_thread()
  os::create_thread(this, thr_type, stack_sz);
  // The thread is still suspended when we reach here. Thread must be explicit started by creator!
}

os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) 包含三个参数,分别是

  1. 线程Thread指针
  2. 线程类型(区别是否是普通线程或是编译线程)
  3. 线程栈大小。

os::create_thread

以linux平台为例,create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) 主要包含创建OSThread,使用pthread_create()创建内核线程,设置线程状态等一系列操作。

/src/os/linux/vm/os_linux.cpp#l850

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
    // Allocate the OSThread object
    OSThread* osthread = new OSThread(NULL, NULL);
    // ...
    osthread->set_thread_type(thr_type);
    // Initial state is ALLOCATED but not INITIALIZED
    osthread->set_state(ALLOCATED);
    // 关联osThread
    thread->set_osthread(osthread);

    // init thread attributes
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    // 栈大小设置相关代码,javathread可用-Xss设置栈大小,必须大于linux的线程栈的最小值,AMD64下最低为64K
    // glibc guard page 用于线程栈的保护页,当线程对栈的写入到达guard page时会出现page fault
    pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));
    // ...
    pthread_t tid;
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
    //...
    // Store pthread info into the OSThread
    osthread->set_pthread_id(tid);

    // Wait until child thread is either initialized or aborted
    // 与子线程进行通信,直到子线程状态不是Allocated
    {
      Monitor* sync_with_child = osthread->startThread_lock();
      // 使用了Mutex::_no_safepoint_check_flag通信
      MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
      while ((state = osthread->get_state()) == ALLOCATED) {
        // 轮询+wait
        sync_with_child->wait(Mutex::_no_safepoint_check_flag);
      }
    }
    // ...
  return true;
}

int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, ( void *)(*start_rtn)( void *), void *arg)包含四个参数。

  1. 指向线程标识符的指针
  2. 指向线程属性的指针
  3. 线程运行函数的起始地址
  4. 运行函数的参数

这里传入的线程运行函数的起始地址是java_start, 具体如下。
/src/os/linux/vm/os_linux.cpp#l791

// Thread start routine for all newly created threads
static void *java_start(Thread *thread) {
    // 子线程运行函数
    // ... cache line相关
    OSThread* osthread = thread->osthread();
    Monitor* sync = osthread->startThread_lock();
    // ...
    // thread_id is kernel thread id (similar to Solaris LWP id)
    // 设置线程Id
    osthread->set_thread_id(os::Linux::gettid());
    //... numa相关代码
    // initialize signal mask for this thread,信号屏蔽掩码
    os::Linux::hotspot_sigmask(thread);
    // initialize floating point control register
    os::Linux::init_thread_fpu_state();
    // handshaking with parent thread
    {
        // 使用Mutex::_no_safepoint_check_flag与父线程通信
        MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
        // notify parent thread
        osthread->set_state(INITIALIZED);
        sync->notify_all();
        // wait until os::start_thread()
        // 轮询+wait,父线程调用os::start_thread转换线程状态
        while (osthread->get_state() == INITIALIZED) {
            sync->wait(Mutex::_no_safepoint_check_flag);
        }
    }
    // call one more level start routine
    thread->run();
    return 0;
}

可以看到,在执行到java_start()的sync->notify_all()之后,父进程就被唤醒了,至此,JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz)就执行完毕了,此时会执行JVM_StartThreadThread::start(native_thread);这一行指令。

Thread::start

此时子线程已经创建完毕,只不过阻塞等待在INITIALIZED状态上,这时父线程执行Thread::start
/src/share/vm/runtime/thread.cpp#l460

void Thread::start(Thread* thread) {
  // ...
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
      // 在java 层面设置为Runnable状态
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}

调用os::start_thread。
/src/share/vm/runtime/os.cpp#l781

void os::start_thread(Thread* thread) {
  // guard suspend/resume
  MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
  OSThread* osthread = thread->osthread();
  osthread->set_state(RUNNABLE);// 到这里osthread状态变为runnble, 只不过子线程在还wait在_no_safepoint_check_flag上
  pd_start_thread(thread);
}

调用pd_start_thread,唤醒子线程,准备运行run()函数。
/src/os/linux/vm/os_linux.cpp#l1031

void os::pd_start_thread(Thread* thread) {
  OSThread * osthread = thread->osthread();
  assert(osthread->get_state() != INITIALIZED, "just checking"); // 此时应该是Runnable
  Monitor* sync_with_child = osthread->startThread_lock();
  MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
  // 唤醒子线程
  sync_with_child->notify();
}

执行完sync_with_child->notify();之后,会执行java_start(Thread *thread)thread->run()语句,对应JavaThread::run()方法,具体如下。

void JavaThread::run() {
    // 线程相关的一些配置
    this->initialize_tlab();
    this->record_base_of_stack_pointer();
    this->record_stack_base_and_size();
    this->initialize_thread_local_storage();
    this->create_stack_guard_pages();
    this->cache_global_variables();
    // ... 
    thread_main_inner();
}

void JavaThread::thread_main_inner() {
    // ...
    if (!this->has_pending_exception() &&
        !java_lang_Thread::is_stillborn(this->threadObj())) {
        // ...
        // 调用entry_point,即前文提到的thread_entry&, 也就是java level中Thread的run()函数
        this->entry_point()(this, this);
    }

    DTRACE_THREAD_PROBE(stop, this);
    // run()函数执行完成,结束子线程
    this->exit(false);
    delete this;
}

到这里线程创建的完整过程就分析完了,可以看到线程的在vm层面的状态经历了 Allocated、Initiliazed、Runnable的状态,这过程中还涉及一些安全点(safepoint)、父子线程通信等的细节,调用的流程图如下所示。
调用流程图