接上文《【运行时数据区】-并发编程-前置知识(4.并发编程基础)-2》,我们得到了2个推论,并且证明了推论1:
- 推论1:此线程非彼线程——JVM层的线程应该是Java线程,操作系统层的线程是真正的系统线程。
- 推论2:此线程与彼线程之间应该存在某种映射关系——JVM层的线程,与操作系统层的系统线程之间,应该存在某种映射关系。
本文跟踪”JVM启动后,产生的JVM侧的main线程与java侧的main线程“的代码,来分析推论2。
1.JVM相关的代码
JVM启动后,会产生如下现象:
JVM本身会产生一系列的系统线程,其中1个线程表示JVM进程的main线程,这个可以通过操作系统层的工具观测到。
JVM在产生操作系统层面的main线程时,会在Java层产生对等的main线程,这个可以通过JDK的工具观测到。
与上述流程相关的JVM源码,如下图:
-3/image-20230211163049820.png)
- main.c:JVM的入口模块
- javac.c:提供了java相关的接口
- java_md_solinux.c:可以简单地认为是java.c中一系列接口在Linux操作系统的实现*(为聚焦本文关注点,简化描述但不严谨)*
- jni.cpp:JVM层(C++语言实现)与JDK层(Java语言实现)之间的接口
- 比如:JDK中很多native接口,可以认为是JVM层与JDK层之间的接口
- thread.cpp:提供了线程的CRUD相关的接口
- os_linux.cpp:可以简单地认为是thread.cpp中一系列接口在Linux操作系统的实现*(为聚焦本文关注点,简化描述但不严谨)*
- vmThread.cpp:比较杂,可以认为是辅助的线程管理模块
- java_md_common.c:比较杂,可以认为是诸如解析java.exe的命令行参数解析等的辅助功能*(为聚焦本文关注点,简化描述但不严谨)
2.JVM的开始-main函数
- 关键源码:
-3/image-20230211163936994.png)
- 代码解读:
- JVM的入口:main.c中有
main函数,这就是JVM的入口 main函数的主体代码不关键:main函数主体代码就是适配不同操作系统的业务逻辑,这部分不关键main函数最后1行代码是关键:调用了java.c的JLI_launch函数
- JVM的入口:main.c中有
- 小结:从这一段代码我们可以覆盖了下图红框中的流程
-3/image-20230211164732122.png)
3.初现端倪-JLI_Launch函数
3.1.JLI_Launch函数主流程
JLI_Launch函数的主流程如下图红色线:
-3/image-20230211165705460.png)
- 流程粗读:笔者先宏观性地理解一下上述流程
- 流程开始:JVM的
main函数初始化JVM的环境,这部分在整体流程中属于辅助流程。 - 流程核心1:加载真正的JVM,JVM核心代码是编译到
libjvm.so中的,这部分是整体流程的重要流程。 - 辅助流程:完成流程核心1后,JVM做了一些辅助任务,这部分是整体流程的辅助流程。
- 流程核心2:创建新的线程,这部分也是整体流程的重要流程。
- 流程开始:JVM的
3.2.初始化执行环境
- 关键代码:
-3/image-20230211165052590.png)
-3/image-20230211171604091.png)
代码解读:
- 在
CreateExecutionEnvironment函数位于java_md_solinux.c中。 - 此函数检索并判断jre的路径、libjvm.so路径、jvm.cfg路径。jre是大家比较熟悉的,我们来看看libjvm.so和jvm.cfg
- jvm的核心代码被编译到libjvm.so中,以动态库的形式提供给JVM各模块调用。
- jvm.cfg内容如下:
-3/image-20230211171140542.png)
- 在
小结:这部分流程不太关键,快速了解即可
-3/image-20230211171303745.png)
3.3.加载JVM
- 关键代码:
-3/image-20230211171659120.png)
-3/image-20230211171711846.png)
代码解读:在java_md_solinux.c的
LoadJavaVM函数有关键的2步- 关键1:通过
dlopen函数加载libjvm.so动态库。 - 关键2:通过
dlsym函数确认并获得libjvm.so中的3个函数句柄,通过这3个函数句柄的名字大致可知是对JVM的创建和获取:- JNI_CreateJavaVM
- JNI_GetDefaultJavaVMInitArgs
- JNI_GetCreatedJavaVMs
- 关键1:通过
小结:此段流程是后续流程的基础,可以跟踪一下JVM源码详细理解一下:
-3/image-20230211172256201.png)
3.4.java.exe的参数/配置等辅助设置
- 关键代码:
-3/image-20230211172956425.png)
-3/image-20230211173009296.png)
-3/image-20230211173022693.png)
-3/image-20230211173033605.png)
-3/image-20230211173046878.png)
-3/image-20230211173056715.png)
代码解读:这部分代码属于辅助流程,根据上述代码截图,基本可以自行理解,此处不赘述
小结:代码阅读理解到这里,我们当前在流程的这个地方(下图红线处):
-3/image-20230211173252080.png)
3.4.创建新线程
- 关键代码:
-3/image-20230211222347685.png)
-3/image-20230211222409464.png)
-3/image-20230211223134192.png)
- 代码解读:
- 当流程执行到java_md_solinux.c的
JVMInit函数时,内部会调用java.c的ContinueInNewThread函数 - java.c的
ContinueInNewThread函数会调用java_md_solinux.c的ContinueInNewThread函数 - java_md_solinux.c的
ContinueInNewThread0函数的内部实现便是创建系统线程、Java线程的关键点了- 此函数调用
pthread库函数,创建Linux上的系统线程,这个就是JVM进程中的main线程
- 此函数调用
- 当流程执行到java_md_solinux.c的
- 小结:本章节解读到下图红色线的流程,至此,我们就找到了JVM启动时与main线程创建的入口了
-3/image-20230211222315626.png)
4.平行世界-JavaMain
根据第3章节,JVM会创建系统线程main,而main线程的线程回调函数就是JavaMain函数,从这个函数名我们大致可以猜测这个函数就代表在JVM层面看到的Java代码层的包含main函数的主类。
我们接下来详细分析一下JavaMain函数:
4.1.JVM创建JavaThread对象
- 关键流程:这条调用链比较复杂,也很关键,关键就在这里:
-3/image-20230211232016635.png)
- java.c的JavaMain()函数:调用
InitializeVM函数
-3/image-20230211230147509.png)
- java.c的InitializeJVM(): 调用jni.cpp的JNI_CreateJavaVM函数。
-3/image-20230211230950971.png)
-3/image-20230211231011066.png)
- jni.cpp的JNI_CreateJavaVM():调用thread.cpp的Threads::create_vm()
-3/image-20230211231026944.png)
- thread.cpp的Threads::create_vm():这里JVM创建了名为
JavaThread的对象,这个对象表示站在JVM层维护的Java线程对象。- 这个对象表示Java代码层的
main线程。 - 创建了这个对象后,调用了thread.cpp的
set_as_starting_thread()
- 这个对象表示Java代码层的
-3/image-20230211231105900.png)
-3/image-20230211231140633.png)
- thread.cpp的
set_as_starting_thread():调用os_linux.cpp的create_main_thread(),传入创建好的JavaThread对象。
-3/image-20230211232635496.png)
- os_linux.cpp的
create_main_thread():调用os_linux.cpp的create_attached_thread()
-3/image-20230211232726011.png)
- os_linux.cpp的
create_attached_thread():创建OSThread对象,记录当前所在的线程对象记录到OSThread对象中,- 当前所在的线程对象就是第3章中讲到的JVM层的系统线程
main线程。
- 当前所在的线程对象就是第3章中讲到的JVM层的系统线程
-3/image-20230211232855155.png)
- 小结:此时在JVM层,已经在操作系统层面创建了2个线程,1个线程是JVM进程自身的主线程,另1个线程就是JVM为Java代码的
main线程创建的操作系统线程。
-3/image-20230211233412288.png)
4.2.JVM创建Java代码侧的Java线程
- 关键流程:
-3/image-20230211233551236.png)
- thread.cpp的
create_initial_thread():JVM通过JNI,创建JDK的java.lang.Thread类的klass对象,进而创建了Java代码侧的java.lang.Thread对象,最后将这个klass对象设置到OSThread对象中。
-3/image-20230211233920369.png)
- 小结:至此,站在JVM层,JVM维护了1个
OSThread对象,这个对象中维护Java的main线程对象和系统线程对象- Java的
main线程对象:- 在Java代码侧,Java程序猿看到的是
java.lang.Thread对象 - 在JVM侧,对应的就是
klass对象
- 在Java代码侧,Java程序猿看到的是
- Java的
5.总结
回到本文一开始的2个推论:
- 推论1:此线程非彼线程——JVM层的线程应该是Java线程,操作系统层的线程是真正的系统线程。
- 推论2:此线程与彼线程之间应该存在某种映射关系——JVM层的线程,与操作系统层的系统线程之间,应该存在某种映射关系。
本文跟踪了JVM创建Java进程的main线程的全流程,可以将推论2进一步明确:
- 推论2(New):从JVM层观测到的线程是Java代码层的Java线程,从操作系统层观测到的JVM层相关的系统线程中,有1个系统线程就对应Java层的Java线程。
- JVM层用1个
OSThread对象维护了pthread创建的系统线程和对应的java.lang.Thread的klass对象。 - JVM层的
java.lang.Thread的klass对象在Java层还对应1个java.lang.Thread对象,这两个对象在各自的层次中表示Java线程。
- JVM层用1个
-3/image-20230211234854597.png)
6.感慨:知识的尽头
玩了1个多月chatGPT,刚开始只认为它不过是AI发展史中的涟漪,谁知chatGPT可能是历史的某个里程碑:
- 笔者在软件行业从业有18年,初次阅读并梳理本文涉及到的"JVM的Java线程与操作系统线程的关系"大约花了4天的时间
- 出于好奇,把这个问题抛给了chatGPT,愕然!
经常感慨计算机世界的知识浩渺无尽,自己却那般无知渺小。
随着文明的发展,浩渺无尽是相对的,对于我是无尽,对于AI可能是有穷。
附上chatGPT对Java主线程与系统线程关系的答案:
-3/image-20230212001032910.png)