接上文《【运行时数据区】-并发编程-前置知识(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源码,如下图:

image-20230211163049820

  • 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函数

  • 关键源码

image-20230211163936994

  • 代码解读:
    • JVM的入口:main.c中有main函数,这就是JVM的入口
    • main函数的主体代码不关键main函数主体代码就是适配不同操作系统的业务逻辑,这部分不关键
    • main函数最后1行代码是关键:调用了java.c的JLI_launch函数
  • 小结:从这一段代码我们可以覆盖了下图红框中的流程

image-20230211164732122

3.初现端倪-JLI_Launch函数

3.1.JLI_Launch函数主流程

  • JLI_Launch函数的主流程如下图红色线:

image-20230211165705460

  • 流程粗读:笔者先宏观性地理解一下上述流程
    • 流程开始:JVM的main函数初始化JVM的环境,这部分在整体流程中属于辅助流程
    • 流程核心1:加载真正的JVM,JVM核心代码是编译到libjvm.so中的,这部分是整体流程的重要流程
    • 辅助流程:完成流程核心1后,JVM做了一些辅助任务,这部分是整体流程的辅助流程
    • 流程核心2:创建新的线程,这部分也是整体流程的重要流程

3.2.初始化执行环境

  • 关键代码

image-20230211165052590

image-20230211171604091

  • 代码解读

    • CreateExecutionEnvironment函数位于java_md_solinux.c中。
    • 此函数检索并判断jre的路径、libjvm.so路径、jvm.cfg路径。jre是大家比较熟悉的,我们来看看libjvm.so和jvm.cfg
    • jvm的核心代码被编译到libjvm.so中,以动态库的形式提供给JVM各模块调用。
    • jvm.cfg内容如下:

    image-20230211171140542

  • 小结:这部分流程不太关键,快速了解即可

image-20230211171303745

3.3.加载JVM

  • 关键代码

image-20230211171659120

image-20230211171711846

  • 代码解读:在java_md_solinux.c的LoadJavaVM函数有关键的2步

    • 关键1:通过dlopen函数加载libjvm.so动态库。
    • 关键2:通过dlsym函数确认并获得libjvm.so中的3个函数句柄,通过这3个函数句柄的名字大致可知是对JVM的创建和获取:
      • JNI_CreateJavaVM
      • JNI_GetDefaultJavaVMInitArgs
      • JNI_GetCreatedJavaVMs
  • 小结:此段流程是后续流程的基础,可以跟踪一下JVM源码详细理解一下:

image-20230211172256201

3.4.java.exe的参数/配置等辅助设置

  • 关键代码

image-20230211172956425

image-20230211173009296

image-20230211173022693

image-20230211173033605

image-20230211173046878

image-20230211173056715

  • 代码解读:这部分代码属于辅助流程,根据上述代码截图,基本可以自行理解,此处不赘述

  • 小结:代码阅读理解到这里,我们当前在流程的这个地方(下图红线处):

image-20230211173252080

3.4.创建新线程

  • 关键代码

image-20230211222347685

image-20230211222409464

image-20230211223134192

  • 代码解读
    • 当流程执行到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线程
  • 小结:本章节解读到下图红色线的流程,至此,我们就找到了JVM启动时与main线程创建的入口了

image-20230211222315626

4.平行世界-JavaMain

根据第3章节,JVM会创建系统线程main,而main线程的线程回调函数就是JavaMain函数,从这个函数名我们大致可以猜测这个函数就代表在JVM层面看到的Java代码层的包含main函数的主类。

我们接下来详细分析一下JavaMain函数:

4.1.JVM创建JavaThread对象

  • 关键流程:这条调用链比较复杂,也很关键,关键就在这里:

image-20230211232016635

  • java.c的JavaMain()函数:调用InitializeVM函数

image-20230211230147509

  • java.c的InitializeJVM(): 调用jni.cpp的JNI_CreateJavaVM函数。

image-20230211230950971

image-20230211231011066

  • jni.cpp的JNI_CreateJavaVM():调用thread.cpp的Threads::create_vm()

image-20230211231026944

  • thread.cpp的Threads::create_vm():这里JVM创建了名为JavaThread的对象,这个对象表示站在JVM层维护的Java线程对象。
    • 这个对象表示Java代码层的main线程。
    • 创建了这个对象后,调用了thread.cpp的set_as_starting_thread()

image-20230211231105900

image-20230211231140633

  • thread.cpp的set_as_starting_thread():调用os_linux.cpp的create_main_thread(),传入创建好的JavaThread对象。

image-20230211232635496

  • os_linux.cpp的create_main_thread():调用os_linux.cpp的create_attached_thread()

image-20230211232726011

  • os_linux.cpp的create_attached_thread():创建OSThread对象,记录当前所在的线程对象记录到OSThread对象中,
    • 当前所在的线程对象就是第3章中讲到的JVM层的系统线程main线程

image-20230211232855155

  • 小结:此时在JVM层,已经在操作系统层面创建了2个线程,1个线程是JVM进程自身的主线程,另1个线程就是JVM为Java代码的main线程创建的操作系统线程。

image-20230211233412288

4.2.JVM创建Java代码侧的Java线程

  • 关键流程

image-20230211233551236

  • thread.cpp的create_initial_thread():JVM通过JNI,创建JDK的java.lang.Thread类的klass对象,进而创建了Java代码侧的java.lang.Thread对象,最后将这个klass对象设置到OSThread对象中。

image-20230211233920369

  • 小结:至此,站在JVM层,JVM维护了1个OSThread对象,这个对象中维护Java的main线程对象系统线程对象
    • Java的main线程对象:
      • 在Java代码侧,Java程序猿看到的是java.lang.Thread对象
      • 在JVM侧,对应的就是klass对象

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.Threadklass对象。
    • JVM层的java.lang.Threadklass对象在Java层还对应1个java.lang.Thread对象,这两个对象在各自的层次中表示Java线程。

image-20230211234854597

6.感慨:知识的尽头

玩了1个多月chatGPT,刚开始只认为它不过是AI发展史中的涟漪,谁知chatGPT可能是历史的某个里程碑:

  • 笔者在软件行业从业有18年,初次阅读并梳理本文涉及到的"JVM的Java线程与操作系统线程的关系"大约花了4天的时间
  • 出于好奇,把这个问题抛给了chatGPT,愕然!

经常感慨计算机世界的知识浩渺无尽,自己却那般无知渺小。

随着文明的发展,浩渺无尽是相对的,对于我是无尽,对于AI可能是有穷。

附上chatGPT对Java主线程与系统线程关系的答案:

image-20230212001032910