JVM线程生命周期的native实现


本文不是入门文章,对读者的理论基础与折腾要求都有点高

  • 了解Java层Thread的各种玩法
  • 知道什么是pthread,什么是系统调用,什么是Glibc/NPTL(Native POSIX Thread Library)
  • 了解操作系统内核中线程的理论流程
  • 需要会配置Clion/VSCode

配置编译参数并测试

首先还是按照上次讲的使用Clion(GDB)调试小型JVM源码方法编译,只是编译参数改一下,以免日志过多

- ./configure --with-classpath-install-dir=/tmp/classpath --enable-trace
+ ./configure --with-classpath-install-dir=/tmp/classpath --enable-tracethread

编译成功后,分别用直接运行与strace进行测试

➜  cd $HOME
➜  $HOME/Desktop/jamvm-2.0.0/src/jamvm -cp . A
....
Hello
➜  strace $HOME/Desktop/jamvm-2.0.0/src/jamvm -cp . A
....
clone(child_stack=0x7f96bf542ff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f96bf5439d0, tls=0x7f96bf543700, child_tidptr=0x7f96bf5439d0) = 17140
....

new Thread创建的源码分析

首先构造一个Java代码创建一个线程,并编译为Class文件

//A.java
class A{
    public static void main(String[] args){
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"233");
            }
        }).start();
    }
}

首先在Java层中使用普通JDK进行断点,并观察断点运行时的线程名称,位置如下

  • start()
  • run()
  • exit()
  • ThreadGroup.remove()断点

接着看Java中线程的源码

通过对Thread的<init>start进行分析,可以发现start中调用了native方法。这里的start需要注意,在OracleJVM中使用了start0作为native方法,而在GNU Classpath/Android中出使用了java.lang.VMThread.create(this, stacksize);实现,本文主要分析java.lang.VMThread.create的实现createJavaThread。

在JVM上通过断点调试可以发现,此native方法被JVM路由到thread.c的createJavaThread

我们在这里打断点

// src/thread.c
void createJavaThread(Object *jThread, long long stack_size) {
   	
  	...
  	Thread *thread = sysMalloc(sizeof(Thread));
	...
  	// native 与 Java 线程的上下文环境进行绑定
    thread->ee = ee;
    ee->thread = jThread;
    ee->stack_size = stack_size;
    ...

    disableSuspend(self);
	// 通过系统调用创建native线程, `threadStart`是创建成功后的回掉函数指针
    if(pthread_create(&thread->tid, &attributes, threadStart, thread)) {
        ...
        return;
    }

  	/* 自旋等待线程变更状态,这里一般来说将会变为 RUNNING */
    pthread_mutex_lock(&lock);
    /* Wait for thread to start */
    while(classlibGetThreadState(thread) == CREATING)
        pthread_cond_wait(&cv, &lock);
    pthread_mutex_unlock(&lock);
    enableSuspend(self);
}

当通过系统调用创建线程完成后,在新的线程中调用threadStart函数指针

// 新线程启动后将立刻调用的函数
void *threadStart(void *arg) {
    Thread *thread = (Thread *)arg;
    Object *jThread = thread->ee->thread;

    /* Parent thread created thread with suspension disabled.
       This is inherited so we need to enable */
    enableSuspend(thread);

    /* Complete initialisation of the thread structure, create the thread
       stack and add the thread to the thread list */
    initThread(thread, INST_DATA(jThread, int, daemon_offset), &thread);

    /* Add thread to thread ID map hash table. */
    addThreadToHash(thread);

    /* Set state to running and notify creating thread */
  	// 设置状态为 RUNNING,通过`pthread_cond_broadcast`结束`pthread_cond_wait`的状态
    signalThreadRunning(thread);

    /* Execute the thread's run method */
  	/* 开始执行Java代码 class=java.lang.Thread,nam=run,sign=`()v` */
    executeMethod(jThread, CLASS_CB(jThread->class)->method_table[run_mtbl_idx]);

    /* Run has completed.  Detach the thread from the VM and exit */
  	/* 这里执行了java.lang.ThreadGroup: removeThread(Ljava/lang/Thread;)V */
    detachThread(thread);

    TRACE("Thread %p id: %d exited\n", thread, thread->id);
    return NULL;
}

如何通过Class引用打印名称 slash2DotsDup(CLASS_CB(class)->name)

可以发现,总体流程是非常清晰的

  1. 主线程通过pthread_create创建新线程
  2. 新线程执行Thread.run() 的Java代码段,执行完成后detach,移除ThreadGroup,结束线程

总的来说,这里分析并不是非常细致,主要是把流程先通起来,后续有具体例子(比如线程池)时再分析

总结

在JamVM中

  • Java线程与pthread线程一一对应,通过内核调度实现。但是不同厂商不一定。
  • 在Java代码中Runnable中的代码段的执行实际上通过pthread_create的函数指针threadStart进行包装后,最终调用executeMethod实现解释字节码

附录

pthead相关知识

线程模型的综述中已经讲过pthread相关知识,这里再回顾一下。JamVM使用pthread的头文件进行开发,在Linux中一般由Glibc实现,最后通过SYSCALL系统调用通过内核去创建线程。

java.lang.Thread
    |
POSIX thread(user mode){
    1. pthread_create(pthread.h,标准库头文件)
    2. bionic/glibc等标准库下的so文件,进行SystemCall
    3. 用户态陷入内核态
}
    |
Kernal thread(kernal mode)

pthead_create参数解释

入参如下

extern int pthread_create (pthread_t *__restrict __newthread,
			   const pthread_attr_t *__restrict __attr,
			   void *(*__start_routine) (void *),
			   void *__restrict __arg) __THROWNL __nonnull ((1, 3));

相关参数

  • _newthread: 即tid, 通过某种算法计算出唯一的Id
  • _attr: 线程类型,初学者可以配置为默认,即NULL
  • _start_routine: 是一个函数指针,类似于动态语言中的闭包(Closure)。线程创建后将执行此函数
  • _arg: 供_start_routine使用的入参

Glibc中NPTL的pthread_create的实现

下次有人再问你线程与进程的区别这种烂大街的问题,你就回答它们就是flag不同,导致(通过COW)共享的内存也不同而已

// glibc-2.25/sysdeps/unix/sysv/linux/createthread.c
const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM
			   | CLONE_SIGHAND | CLONE_THREAD
			   | CLONE_SETTLS | CLONE_PARENT_SETTID
			   | CLONE_CHILD_CLEARTID
			   | 0);

TLS_DEFINE_INIT_TP (tp, pd);

// 此部分调用 external `__clone`
if (__glibc_unlikely (ARCH_CLONE (&start_thread, STACK_VARIABLES_ARGS,
                                  clone_flags, pd, &pd->tid, tp, &pd->tid)
                      == -1))
  return errno;

__clone的实现在内核中,通过 x86_64 ABI与网上找的例子,最终汇编代码如下

eax = 120 (syscall number for sys_clone)
ebx = unsigned long flags
ecx = void *child_stack
edx = void *ptid
esi = void *ctid
edi = struct pt_regs *regs  
int 80H

内核收到80H中断请求后,将进行内核态线程的创建与调度,后面内核线程以后再写...

JVM中自动启动的线程

在JVM中,创建了main线程(Native线程,对应 JVM下的用户主线程) + 如下4个Java-level的线程 ,可以通过对createVMThread进行findUsage定位

1. GC线程

通过WhileLoop与Sleep每隔1000ms进行一次GC,这里后期将专门分析GC流程(当初买的一本GC实现终于可以用啦)

/* Create and start VM thread for asynchronous GC */
if(args->asyncgc)
  createVMThread("Async GC", asyncGCThreadLoop);
2. Reference处理线程

这里配合GC的线程,共2个,日后在详细讲

/* Create and start VM threads for the reference handler and finalizer */
createVMThread("Finalizer", finalizerThreadLoop);
createVMThread("Reference Handler", referenceHandlerThreadLoop);
3. 信号处理线程

通过WhileLoop+sigwait()系统阻塞监听信号实现处理 SIGINTSIGQUIT

 /* Create the signal handler thread.  It is responsible for
 catching and handling SIGQUIT (thread dump) and SIGINT
 (user-termination of the VM, e.g. via Ctrl-C).  Note it
 must be a valid Java-level thread as it needs to run the
 shutdown hooks in the event of user-termination */
createVMThread("Signal Handler", classlibSignalThread);

参考