首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java编程思想之多线程(四)

调试环境

openjdk version "9.0.4-internal"

OpenJDK Runtime Environment (slowdebug build 9.0.4-internal+0-adhoc.$hostname.jdk9u-1b1226687b89)

OpenJDK 64-Bit Server VM (slowdebug build 9.0.4-internal+0-adhoc.$hostname.jdk9u-1b1226687b89, mixed mode)

mac OS high sierra version 10.13.4

clion 2018.1

lldb-470.99.0

调试目标

从源码层面认识java程序在JVM内部的启动过程

理解java线程在JVM内部的实现机制

理解java线程与内核线程的映射关系

测试程序

如下图:

测试程序Test1.java在主线程中打印线程名称,之后修改线程名称,调用startThread()方法声明一个新的线程,设定线程名称为“bbbb”,启动该线程。名为“bbbb”的线程中首先打印3次线程名称,然后修改线程名称为“aaaa”,并输出新的线程名称。

调试过程

编译Test1.java程序:

~/Desktop/jdk9u-1b1226687b89/build/macosx-x86_64-normal-server-slowdebug/images/jdk/bin/javac Test1.java

在clion中导入openjdk9u源码,调试中会用到jdk和hotspot两个分支,如下图:

配置debug环境,其中target对应工程名,executable对应~/Desktop/jdk9u-1b1226687b89/build/macosx-x86_64-normal-server-slowdebug/images/jdk/bin/java文件,Program argument对应executable的程序参数,包括class文件名和必要的执行参数,例如:-XX参数,class main函数的参数等,Working directory对应class文件的目录,放在~/Desktop,见下图:

编译Test1.java:~/Desktop/jdk9u-1b1226687b89/build/macosx-x86_64-normal-server-slowdebug/images/jdk/bin/javac Test1.java,运行~/Desktop/jdk9u-1b1226687b89/build/macosx-x86_64-normal-server-slowdebug/images/jdk/bin/java Test1查看执行结果:

启动debug,如下图,注意:启动后,需要设置lldb,process handle SIGSEGV --stop=false,否则出现segsegv错误时,会hang住debug过程。从图中可以看到,java程序从jdk launcher下的main.c启动,argv指向java的输入参数,此时启动一个本地线程Thread-1-。

launcher调用hotspot的jni.cpp,创建java VM。从下图中可以看到它是通过jni接口调用创建JVM的,Thread-5是JDK的launcher创建的一个本地线程,继续后面的实验我们就会知道它对应的就是java中的main线程,而从java程序的层面看到的只是该线程的部分表象。

从jni.cpp跳转到thread.cpp,执行JVM的初始化,创建JVM的main线程,创建VMThread,创建GC线程等。一下子创建如此多的线程,想必大家有懵圈的感觉,的确如此,Thread.cpp中的jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain)方法非常重要,它承担了JVM初始化的工作,像java程序里的类加载,main函数的启动,GC的启动,java线程与内核线程的映射都是在这个方法中完成。因此该方法的调试是理解JVM中线程管理的关键,是深入理解各个线程工作过程,JVM线程调度等的入口。

创建main线程对应的内核线程,并把main线程attaching到新建的内核线程上。这里大家一定会有疑问,Thread-5不已经是主线程了吗,为何又要创建主线程?JavaThread又是什么鬼?对应的内核线程在哪呢?这几个问题的答案都在下图中的几行代码中了。首先Thread-5的确是主线程了,它负责创建VM实例,VM初始化,创建VM其他后台线程,但它暂时还没有生成内核线程(即OS线程或OSThread,两者在本文中等价);而JavaThread是thread类的一个子类,JVM中thread类是所有线程对象的根类。JavaThread类的对象main_thread创建时会把Thread-5和新创建一个OS线程关联起来。JavaThread对象是java层面线程在JVM内部的代理,它们之间为一一对应关系,JavaThread和OSThread也是一一对应关系,从而java线程和OS线程也是一一对应关系。JavaThread在JVM内部也被称为NativeThread。

修改主线程名称前,未显示调用java线程的setName之前,JVM会分配一个默认的java线程名,main线程的名称就是“main”,而对应的主线程Thread-5并不会改变,这是因为java线程和JavaThread直接对应,java线程的名称直接对应的是JavaThread的名称。而java中main线程比较特殊,它除了对应一个JavaThread线程还有一个本地线程即Thread-5线程,本地线程的名称格式为:“[java:$JavaThread名称]”。

修改主线程名称后,Thread-5的名称也已经改变。另外源码中jthread对应jni里面的java线程引用,它负责连接java线程和jvm。

另外一个java线程名称也已经修改,

最终运行结果,

调试结论

java程序的启动过程是先从jdk的launcher的main.c开始,xos下会启动两个线程,参见实验中的thread-1和thread-2;通过jni,launcher创建jvm实例,启动主线程,这个主线程也是jvm主线程,包含了java程序对应的main线程(对应一个JavaThread线程),并attaching到一个OSThread。

Java层面线程在JVM内部的实现过程是,首先java程序通过一个jni对象jobject,源码中字段名称为jthread,但事实上它不是一个thread,而且每次线程调用时,jobject都不同,只是通过jobject查找到oop(ordinary object pointer,普通对象指针,描述对象实例信息),通过oop定位到JavaThread对象,即整个的调用过程是:

java thread->jni object->oop->JavaThread(native thread)->OS thread。

JVM规范并未明确规定java线程和内核线程的映射关系,可以是一对一,多对一,或多对多。Hotspot采用的是一对一关系。

JVM中线程类型,继承关系如下图:

JavaThread的状态如下图几种(参见globalDefinitions.hpp),相对每一种state,_trans的值相应增1:

到此,JVM内部的线程调试告一段落。本系列文章的目的是关注java层面的多线程编程,理解其原理,揭示可能遇到的坑,因此JVM内部的多线程管理机制暂不过多研究,以后做为专题再深入学习。

本文调试视频:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180916G0FAEP00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券