• Java虚拟机启动整体流程和基础学习(内容很多,不可快餐阅读),推理+源码论证


    不啰嗦,直接看:

    ISA指令强关联CPU平台,那么汇编语言也一定强关联CPU平台,而C语言又是对汇编最简单的抽象,也就一定会耦合不同CPU平台的代码,这也就是意味着,在写C/C++项目的时候,必须要考虑不同平台的代码编写差异,要根据不同的平台写不同的代码以满足不同平台的功能使用与相关优化。

    在这里插入图片描述

    而我们知道程序想要操作硬件就必须要经过操作系统,由操作系统给定的系统调用去进行硬件上的操作

    OS内核是在进程的虚拟地址的1GB空间,作为进程的内核态来执行,也就是可以理解为一串代码,不是独立的执行的软件

    在这里插入图片描述

    也就是说,如果想要一个App能在不同的操作系统上运行,那么必须要有不同的操作系统宏处理或者文件以适配不同系统的系统调用

    除了通过OS的系统调用操作计算机硬件外,还可以通过内联汇编直接操作CPU

    对于C/C++开发人员来讲,会有一个很重要的问题需要去考虑:指针问题

    野指针(指向不明空间/无效内存),内存泄漏(忘记释放),内存溢出等问题

    为了规避这种操作指针操作带来的问题,那么就有两种解决方案:

    1. 让编译器识别指针问题,但是这个方式也带来不足之处,由于如果出现任何指针问题则编译不通过,使得语言编写相对死板,就不能使用一些特殊的写法让代码量减少

    2. 自动释放指针,也即垃圾回收机制。基于此想法,便出现了Java

    那么Java编译器该怎么设计呢?

    直接生成 ISA指令集?是可以的,但是Java语言开发者是想要适配多个不同的CPU平台,适配不同OS,如果直接生成ISA,那岂不是和其他的静态语言一样了?

    那么是将Java编译生成抽象语法树,然后用C/C++去识别和处理这个抽象语法树?亦或者是将Java编译生成一个中间语言(或称中间表示)然后交由c/c++去处理呢?

    很显然,Java选择了后者,为什么呢??动态性,如果选择抽象语法树,那么所有的动态性全都需要在c++引擎层面去实现,而使用中间语言则可以让Java语言与C++脱钩,只需要C++识别处理中间语言即可

    这个中间语言便是熟知的 字节码

    最终便得到了:

    在这里插入图片描述

    用C++代码解释字节码文件,但是这样就会出现一个问题,c++读取字节码后遍历字节码,然后条件判断再进行不同的处理

    但是这样性能不高,即便C++再怎么优化也不如直接生成汇编语言来的快,甚至可以预先将汇编语言生成好机器码,然后直接拿机器码去运行

    因此,就有了如下几种机制:

    C++解释字节码

    字节码直接映射成机器码执行,由机器码解释

    动态编译优化(JIT)

    运行速度:

    C++解释 < 机器码解释 < 编译优化

    启动速度:

    C++解释 > 机器码解释 > 编译优化

    根据热点的方法进行选择性编译,也就是:

    1、按照解释执行启动方法

    2、根据调用的热点(Hotspot)方法 运行 JIT 动态编译优化

    GCC存在 -O1、- O2 、-O3等优化等级,每一级的优化速度不同,性能也不同,那么在JIT中引入此方式,得到分层编译,既满足编译速度也执行的速度

    C++在不同的操作系统上的处理不同,因此图变如下:

    在这里插入图片描述

    使得Java程序无需关心底层不同OS平台是如何处理的,只需要专注于业务代码的编写即可

    也即:Write Once,Run Everywhere,跨平台

    所以Java只需要面向字节码编程即可

    那既然要解释字节码,若不同虚拟机实现字节码的方式不同,就会回到C/C++不同平台不同处理,那么是不是需要有一定的规范如何去解释?

    《Java虚拟机规范》约束不同虚拟机厂商对于该字节码的约定与解释

    由于字节码底层操作的对象需要处理,那么因此引入了垃圾回收机制(GC模块)

    无需JAVA程序员做GC的工作,在GC模块中实现自动垃圾回收

    Java 字节码解析:

    public class Hello{
        
        public static void main(String[] args){
            System.out.println("Hello World");
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们都知道,在C语言的程序中,函数传参是放在栈帧或者寄存器中,字符串存放在.data下的.string 静态数据域中

    那我们可以猜测一下,Java的字节码会不会和C语言的存放格式差不多呢?

    使用javac命令编译Hello.java得到 Hello.class字节码文件,使用javap -v 命令查看字节码信息

    Classfile /D:/桌面/Hello.class
      Last modified 2022-9-29; size 415 bytes
      MD5 checksum ba0701759adea1ab8a220d2cded9f997
      Compiled from "Hello.java"
    public class Hello
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#15         // java/lang/Object."":()V
       #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
       #3 = String             #18            // Hello world
       #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #5 = Class              #21            // Hello
       #6 = Class              #22            // java/lang/Object
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               main
      #12 = Utf8               ([Ljava/lang/String;)V
      #13 = Utf8               SourceFile
      #14 = Utf8               Hello.java
      #15 = NameAndType        #7:#8          // "":()V
      #16 = Class              #23            // java/lang/System
      #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
      #18 = Utf8               Hello world
      #19 = Class              #26            // java/io/PrintStream
      #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
      #21 = Utf8               Hello
      #22 = Utf8               java/lang/Object
      #23 = Utf8               java/lang/System
      #24 = Utf8               out
      #25 = Utf8               Ljava/io/PrintStream;
      #26 = Utf8               java/io/PrintStream
      #27 = Utf8               println
      #28 = Utf8               (Ljava/lang/String;)V
    {
      public Hello();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."":()V
             4: return
          LineNumberTable:
            line 1: 0
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #3                  // String Hello world
             5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 5: 0
            line 7: 8
    }
    SourceFile: "Hello.java"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    这样一比较,和ELF文件展示方式一样啊

    那么我们需要解析这个字节码文件,如何解析呢,我得知道排布格式吧?前面说到了《java虚拟机规范》定义的啊

    在这里插入图片描述

    规范定义什么,那底层实现就是什么,与操作系统和CPU平台无关

    也就是我可以通过读入文件数据流,按照以上格式进行解析,即可得到字节码文件中的相关信息

    那按照这个虚拟机规范,hotspot是怎么解析这个classfile的呢?

    猜测一下,是遍历整个文件,然后按照对应的字节长度进行判断解析

    在这里插入图片描述

    Java 加载,链接,初始化

    Java没有静态链接,只有动态链接

    而在C语言中,.so文件的GOT表和PLT表进行跳转链接,那Java中的动态链接同样需要找表

    在字节码中的constantPool就是ELF文件中的DynamicSymbol

    如何做的呢?

    读入代码段中的数据,找到invokespecial 对应的 constantPool中数据对应的地址解析出来,再把地址填进去执行即可

    由于我们知道,需要按照一定的格式来读取,那么我们需要考虑将这些数据保存起来

    也就是需要有个容器来存放ClassFile Structure,猜测应该是放在与ClassFile相关的c++代码中

    而在Hotspot中没有直接的classfile.cpp,而与之相关的只有classFileParser.cpp、classFileStream.cpp前者意思是字节码解析器,那么肯定与字节码处理相关的部分的代码, 而后者是字节码文件流,肯定是用来帮助读取字节码数据流的,因此我们得看classFileParser.cpp

    在这里插入图片描述

    在其中的parseClassFile方法中

    在这里插入图片描述

    通过查找其函数体,我们可以找到如下代码:

    在这里插入图片描述

    分配实例类空间

    也就是生成了一个InstanceKlass

    在这里插入图片描述

    在这里插入图片描述

    Klass的描述为:

    在这里插入图片描述

    Klass的规定

    1. 语言等级的class对象(方法字典等等)
    2. 提供虚拟机对于对象的分派行为

    在一个C++类中存在这两个行为

    所以说明,我们之前要找的字节码解析完后就存放在这个地方

    而我们知道对象有两个部分,一个是属性(field),一个是方法(method)

    那ClassFileParser肯定是有这两个部分的处理逻辑的,而且按照面向对象的思想,这两个肯定也会分别存储在某个容器中

    在这里插入图片描述

    看到这里我们分别要去看看parse_fields的产物和parse_methods的产物

    在这里插入图片描述

    根据上图,字节码属性的信息存放在了FieldInfo中

    在这里插入图片描述

    按照手册规定

    在这里插入图片描述

    parse_methods方法中的产物如下:

    在这里插入图片描述

    也就是将方法数据存放在了Method中:

    在这里插入图片描述

    那么除了方法和属性之外,还得需要有个存放静态数据的地方吧?也即常量池

    解析常量池的方法是

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    最终可以得到如下图:

    在这里插入图片描述

    对于单独的一个类来讲,在Java中是没有什么太大作用的,那么就肯定是多个类,而多个类就肯定会相互关联

    上图将会变化为下面这样

    在这里插入图片描述

    那么类与类之间是怎么关联在一起的呢??

    在ELF中我们知道是有动态链接库和静态链接库,然后通过PLT和GOT 拼接组装生成可执行文件

    是不是极度相似呢?

    拿前面的字节码来看

    Classfile /D:/桌面/Hello.class
      Last modified 2022-9-29; size 415 bytes
      MD5 checksum ba0701759adea1ab8a220d2cded9f997
      Compiled from "Hello.java"
    public class Hello
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#15         // java/lang/Object."":()V
       #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
       #3 = String             #18            // Hello world
       #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #5 = Class              #21            // Hello
       #6 = Class              #22            // java/lang/Object
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               main
      #12 = Utf8               ([Ljava/lang/String;)V
      #13 = Utf8               SourceFile
      #14 = Utf8               Hello.java
      #15 = NameAndType        #7:#8          // "":()V
      #16 = Class              #23            // java/lang/System
      #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
      #18 = Utf8               Hello world
      #19 = Class              #26            // java/io/PrintStream
      #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
      #21 = Utf8               Hello
      #22 = Utf8               java/lang/Object
      #23 = Utf8               java/lang/System
      #24 = Utf8               out
      #25 = Utf8               Ljava/io/PrintStream;
      #26 = Utf8               java/io/PrintStream
      #27 = Utf8               println
      #28 = Utf8               (Ljava/lang/String;)V
    {
      public Hello();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."":()V
             4: return
          LineNumberTable:
            line 1: 0
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #3                  // String Hello world
             5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 5: 0
            line 7: 8
    }
    SourceFile: "Hello.java"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    例如,getstatic 后面的#2仅仅是个占位符,此时并没有具体的地址数据,而在常量池表中#2 引用了#16和#17,同样的,这里并没有具体的地址数据值

    那不就是和ELF中的动态加载吗?先用一个无用的地址先占着,之后再通过GOT把真实的值填上

    所以 ConstantPool就是等同于维护了ELF中的符号表(Dynamic_Symbol)的元数据信息

    在Java源码文件结构图如下:

    在这里插入图片描述

    而我们可以很清楚的知道,作为一个程序员,是不是应该考虑将Java虚拟机源码和操作Java虚拟机的工具代码分开编译??

    所以就是拆分出来了 hotspot文件夹和jdk文件夹

    根据elf文件来理解,hotspot是属于Java虚拟机,工具可以共同操作的,因此hotspot虚拟机应该打包成.so/.dll动态链接库文件

    而jdk中的.c文件则编译成可执行文件

    Klass 类表示整个字节码读入内存中的容器信息

    ConstantPool 类表示Java字节码所操作的对象的符号信息

    Method类 表示Java字节码的方法描述信息

    FieldInfo 表示Java的成员变量信息

    在这里插入图片描述

    而我们知道,在jvm中两大板块:执行引擎+GC

    执行引擎如何加载Klass呢?因此需要用到类加载器

    如此,得到下面这幅图:

    在这里插入图片描述

    类加载器读入字节码文件,然后创建上面的Klass信息,传递给执行引擎,最后用GC进行垃圾回收

    由于Java堆内存的管理是由universe进行管理

    所以universe主要是两个模块:

    1. 针对对象的创建:内存管理模块,空间分配
    2. 针对对象的销毁:GC模块

    在openJDK源码包下的目录层级如下:

    在这里插入图片描述

    其中:

    common : 一些公共文件,比如下载源码的shell脚本、生成make的autoconf等文件

    corba : 分布式通讯接口

    hotspot : Java虚拟机实现

    jaxp : xml文件处理代码

    jaxws : ws实现api

    jdk : 用于操作虚拟机的工具代码,也即jdk源码

    langtools : java语言的工具实现的用于测试的代码,基础实现类等,javac,java,javap等 打包成 tools.jar

    make : make编译文件夹

    nashorn : java中的js运行实现

    test : 测试包

    而我们研究的重点放在hotspot虚拟机的实现上以及jdk虚拟机操作工具的源码上

    也就是通过jdk包中的工具来引导执行引擎

    在这里插入图片描述

    如何引导?使用动态链接库

    而动态链接库又是数据独立,代码共享的,所以使用工具命令可以调用到执行引擎的main

    那么执行引擎的main方法是怎么样的呢?由于我们只研究在linux平台的,所以会屏蔽掉windows平台的一些代码,引导型的启动代码都是C语言代码

    main.c

    int main(int argc, char **argv)
    {
        int margc;
        char** margv;
        const jboolean const_javaw = JNI_FALSE;
        margc = argc;
        margv = argv;
        // JLI_Launch()函数进行了一系列必要的操作,
        //  如libjvm.so的加载、参数解析、Classpath的 获取和设置、系统属性设置、JVM初始化等。
        //  调用LoadJavaVM()加载libjvm.so并初始化相关参数
        return JLI_Launch(margc, margv,
                       sizeof(const_jargs) / sizeof(char *), const_jargs,
                       sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
                       FULL_VERSION,
                       DOT_VERSION,
                       (const_progname != NULL) ? const_progname : *margv,
                       (const_launcher != NULL) ? const_launcher : *margv,
                       (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
                       const_cpwildcard, const_javaw, const_ergo_class);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    我们需要找到动态链接库的dlopen打开文件,dlsym查找符号,以及dlclose关闭 的相关处理

    java.c

    int
    JLI_Launch(int argc, char ** argv,              /* main argc, argc */
            int jargc, const char** jargv,          /* java args */
            int appclassc, const char** appclassv,  /* app classpath */
            const char* fullversion,                /* full version defined */
            const char* dotversion,                 /* dot version defined */
            const char* pname,                      /* program name */
            const char* lname,                      /* launcher name */
            jboolean javaargs,                      /* JAVA_ARGS */
            jboolean cpwildcard,                    /* classpath wildcard*/
            jboolean javaw,                         /* windows-only javaw */
            jint ergo                               /* ergonomics class policy */
    )
    {
        int mode = LM_UNKNOWN;
        char *what = NULL;
        char *cpath = 0;
        char *main_class = NULL;
        int ret;
        InvocationFunctions ifn;
        jlong start, end;
        char jvmpath[MAXPATHLEN];
        char jrepath[MAXPATHLEN];
        char jvmcfg[MAXPATHLEN];
    
        _fVersion = fullversion;
        _dVersion = dotversion;
        _launcher_name = lname;
        _program_name = pname;
        _is_java_args = javaargs;
        _wc_enabled = cpwildcard;
        _ergo_policy = ergo;
    
        // 初始化 执行程序
        InitLauncher(javaw);
        // 导出当前执行状态
        DumpState();
        // 如果JLI是追踪执行程序
        if (JLI_IsTraceLauncher()) {
            int i;
            printf("Command line args:\n");
            for (i = 0; i < argc ; i++) {
                printf("argv[%d] = %s\n", i, argv[i]);
            }
            // 添加执行指令
            AddOption("-Dsun.java.launcher.diag=true", NULL);
        }
    
        /*
         * Make sure the specified version of the JRE is running.
         *
         * There are three things to note about the SelectVersion() routine:
         *  1) If the version running isn't correct, this routine doesn't
         *     return (either the correct version has been exec'd or an error
         *     was issued).
         *  2) Argc and Argv in this scope are *not* altered by this routine.
         *     It is the responsibility of subsequent code to ignore the
         *     arguments handled by this routine.
         *  3) As a side-effect, the variable "main_class" is guaranteed to
         *     be set (if it should ever be set).  This isn't exactly the
         *     poster child for structured programming, but it is a small
         *     price to pay for not processing a jar file operand twice.
         *     (Note: This side effect has been disabled.  See comment on
         *     bugid 5030265 below.)
         */
        SelectVersion(argc, argv, &main_class);
    	// 创建执行环境
        CreateExecutionEnvironment(&argc, &argv,
                                   jrepath, sizeof(jrepath),
                                   jvmpath, sizeof(jvmpath),
                                   jvmcfg,  sizeof(jvmcfg));
    
        ifn.CreateJavaVM = 0;
        ifn.GetDefaultJavaVMInitArgs = 0;
    
        if (JLI_IsTraceLauncher()) {
            start = CounterGet();
        }
        // 加载Java虚拟机
        if (!LoadJavaVM(jvmpath, &ifn)) {
            return(6);
        }
    
        if (JLI_IsTraceLauncher()) {
            end   = CounterGet();
        }
    
        JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
                 (long)(jint)Counter2Micros(end-start));
    
        ++argv;
        --argc;
    
        if (IsJavaArgs()) {
            /* Preprocess wrapper arguments */
            TranslateApplicationArgs(jargc, jargv, &argc, &argv);
            if (!AddApplicationOptions(appclassc, appclassv)) {
                return(1);
            }
        } else {
            /* Set default CLASSPATH */
            cpath = getenv("CLASSPATH");
            if (cpath == NULL) {
                cpath = ".";
            }
            SetClassPath(cpath);
        }
    
        /* Parse command line options; if the return value of
         * ParseArguments is false, the program should exit.
         */
        if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
        {
            return(ret);
        }
    
        /* Override class path if -jar flag was specified */
        if (mode == LM_JAR) {
            SetClassPath(what);     /* Override class path */
        }
    
        /* set the -Dsun.java.command pseudo property */
        SetJavaCommandLineProp(what, argc, argv);
    
        /* Set the -Dsun.java.launcher pseudo property */
        SetJavaLauncherProp();
    
        /* set the -Dsun.java.launcher.* platform properties */
        SetJavaLauncherPlatformProps();
    
        return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132

    详细去看看CreateExecutionEnvironment方法是如何创建执行环境的?

    java_md_solinux.c

    void
    CreateExecutionEnvironment(int *pargc, char ***pargv,
                               char jrepath[], jint so_jrepath,
                               char jvmpath[], jint so_jvmpath,
                               char jvmcfg[],  jint so_jvmcfg) {
      /*
       * First, determine if we are running the desired data model.  If we
       * are running the desired data model, all the error messages
       * associated with calling GetJREPath, ReadKnownVMs, etc. should be
       * output.  However, if we are not running the desired data model,
       * some of the errors should be suppressed since it is more
       * informative to issue an error message based on whether or not the
       * os/processor combination has dual mode capabilities.
       */
        jboolean jvmpathExists;
    
        /* Compute/set the name of the executable */
        SetExecname(*pargv);
    
        /* Check data model flags, and exec process, if needed */
        {
          char *arch        = (char *)GetArch(); /* like sparc or sparcv9 */
          char * jvmtype    = NULL;
          int  argc         = *pargc;
          char **argv       = *pargv;
          int running       = CURRENT_DATA_MODEL;
    
          int wanted        = running;      /* What data mode is being
                                               asked for? Current model is
                                               fine unless another model
                                               is asked for */
          jboolean mustsetenv = JNI_FALSE;
          char *runpath     = NULL; /* existing effective LD_LIBRARY_PATH setting */
          char* new_runpath = NULL; /* desired new LD_LIBRARY_PATH string */
          char* newpath     = NULL; /* path on new LD_LIBRARY_PATH */
          char* lastslash   = NULL;
          char** newenvp    = NULL; /* current environment */
    
          char** newargv    = NULL;
          int    newargc    = 0;
    
          /*
           * Starting in 1.5, all unix platforms accept the -d32 and -d64
           * options.  On platforms where only one data-model is supported
           * (e.g. ia-64 Linux), using the flag for the other data model is
           * an error and will terminate the program.
           */
    
          { /* open new scope to declare local variables */
            int i;
    
            newargv = (char **)JLI_MemAlloc((argc+1) * sizeof(char*)); // 将传入的值存放在一个新的变量中
            newargv[newargc++] = argv[0];
    
            /* scan for data model arguments and remove from argument list;
               last occurrence determines desired data model */
            for (i=1; i < argc; i++) {
    
              if (JLI_StrCmp(argv[i], "-J-d64") == 0 || JLI_StrCmp(argv[i], "-d64") == 0) {
                wanted = 64;
                continue;
              }
              if (JLI_StrCmp(argv[i], "-J-d32") == 0 || JLI_StrCmp(argv[i], "-d32") == 0) {
                wanted = 32;
                continue;
              }
              newargv[newargc++] = argv[i];
    
              if (IsJavaArgs()) {
                if (argv[i][0] != '-') continue;
              } else {
                if (JLI_StrCmp(argv[i], "-classpath") == 0 || JLI_StrCmp(argv[i], "-cp") == 0) { // 自动添加了classpath
                  i++;
                  if (i >= argc) break;
                  newargv[newargc++] = argv[i];
                  continue;
                }
                if (argv[i][0] != '-') { i++; break; }
              }
            }
    
            /* copy rest of args [i .. argc) */
            while (i < argc) {
              newargv[newargc++] = argv[i++];
            }
            newargv[newargc] = NULL;
    
            /*
             * newargv has all proper arguments here
             */
    
            argc = newargc;
            argv = newargv;
          }
    
          /* If the data model is not changing, it is an error if the
             jvmpath does not exist */
          if (wanted == running) {
            /* Find out where the JRE is that we will be using. */
            if (!GetJREPath(jrepath, so_jrepath, arch, JNI_FALSE) ) {  // 找到jre的路径
              JLI_ReportErrorMessage(JRE_ERROR1);
              exit(2);
            }
            JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%s%sjvm.cfg",
                         jrepath, FILESEP, FILESEP,  arch, FILESEP);
            /* Find the specified JVM type */
            if (ReadKnownVMs(jvmcfg, JNI_FALSE) < 1) {
              JLI_ReportErrorMessage(CFG_ERROR7);
              exit(1);
            }
    
            jvmpath[0] = '\0';
            jvmtype = CheckJvmType(pargc, pargv, JNI_FALSE);
            if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
                JLI_ReportErrorMessage(CFG_ERROR9);
                exit(4);
            }
    
            if (!GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, arch, 0 )) {
              JLI_ReportErrorMessage(CFG_ERROR8, jvmtype, jvmpath);
              exit(4);
            }
            /*
             * we seem to have everything we need, so without further ado
             * we return back, otherwise proceed to set the environment.
             */
            mustsetenv = RequiresSetenv(wanted, jvmpath);
            JLI_TraceLauncher("mustsetenv: %s\n", mustsetenv ? "TRUE" : "FALSE");
    
            if (mustsetenv == JNI_FALSE) {
                JLI_MemFree(newargv);
                return;
            }
            JLI_MemFree(newargv);
            return;
          } else {  /* do the same speculatively or exit */
            if (running != wanted) {
              /* Find out where the JRE is that we will be using. */
              if (!GetJREPath(jrepath, so_jrepath, GetArchPath(wanted), JNI_TRUE)) {
                /* give up and let other code report error message */
                JLI_ReportErrorMessage(JRE_ERROR2, wanted);
                exit(1);
              }
              JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%s%sjvm.cfg",
                           jrepath, FILESEP, FILESEP, GetArchPath(wanted), FILESEP);
              /*
               * Read in jvm.cfg for target data model and process vm
               * selection options.
               */
              if (ReadKnownVMs(jvmcfg, JNI_TRUE) < 1) {
                /* give up and let other code report error message */
                JLI_ReportErrorMessage(JRE_ERROR2, wanted);
                exit(1);
              }
              jvmpath[0] = '\0';
              jvmtype = CheckJvmType(pargc, pargv, JNI_TRUE);
              if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
                JLI_ReportErrorMessage(CFG_ERROR9);
                exit(4);
              }
    
              /* exec child can do error checking on the existence of the path */
              jvmpathExists = GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, GetArchPath(wanted), 0);
    
              mustsetenv = RequiresSetenv(wanted, jvmpath);
            }
    
            if (mustsetenv) {
                /*
                 * We will set the LD_LIBRARY_PATH as follows:
                 *
                 *     o          $JVMPATH (directory portion only)
                 *     o          $JRE/lib/$LIBARCHNAME
                 *     o          $JRE/../lib/$LIBARCHNAME
                 *
                 * followed by the user's previous effective LD_LIBRARY_PATH, if
                 * any.
                 */
    
                /* runpath contains current effective LD_LIBRARY_PATH setting */
    
                jvmpath = JLI_StringDup(jvmpath);
                new_runpath = JLI_MemAlloc(((runpath != NULL) ? JLI_StrLen(runpath) : 0) +
                        2 * JLI_StrLen(jrepath) + 2 * JLI_StrLen(arch) +
                        JLI_StrLen(jvmpath) + 52);
                newpath = new_runpath + JLI_StrLen("LD_LIBRARY_PATH=");
    
    
                /*
                 * Create desired LD_LIBRARY_PATH value for target data model.
                 */
                {
                    /* remove the name of the .so from the JVM path */
                    lastslash = JLI_StrRChr(jvmpath, '/');
                    if (lastslash)
                        *lastslash = '\0';
    
                    sprintf(new_runpath, "LD_LIBRARY_PATH="
                            "%s:"
                            "%s/lib/%s:"
                            "%s/../lib/%s",
                            jvmpath,
                            jrepath, GetArchPath(wanted),
                            jrepath, GetArchPath(wanted)
                            );
    
    
                    /*
                     * Check to make sure that the prefix of the current path is the
                     * desired environment variable setting, though the RequiresSetenv
                     * checks if the desired runpath exists, this logic does a more
                     * comprehensive check.
                     */
                    if (runpath != NULL &&
                            JLI_StrNCmp(newpath, runpath, JLI_StrLen(newpath)) == 0 &&
                            (runpath[JLI_StrLen(newpath)] == 0 || runpath[JLI_StrLen(newpath)] == ':') &&
                            (running == wanted) /* data model does not have to be changed */
                            ) {
                        JLI_MemFree(newargv);
                        JLI_MemFree(new_runpath);
                        return;
                    }
                }
    
                /*
                 * Place the desired environment setting onto the prefix of
                 * LD_LIBRARY_PATH.  Note that this prevents any possible infinite
                 * loop of execv() because we test for the prefix, above.
                 */
                if (runpath != 0) {
                    JLI_StrCat(new_runpath, ":");
                    JLI_StrCat(new_runpath, runpath);
                }
    
                if (putenv(new_runpath) != 0) {
                    exit(1); /* problem allocating memory; LD_LIBRARY_PATH not set
                        properly */
                }
    
                /*
                 * Unix systems document that they look at LD_LIBRARY_PATH only
                 * once at startup, so we have to re-exec the current executable
                 * to get the changed environment variable to have an effect.
                 */
    
                newenvp = environ;
            }
            {
                char *newexec = execname;
                JLI_TraceLauncher("TRACER_MARKER:About to EXEC\n");
                (void) fflush(stdout);
                (void) fflush(stderr);
                if (mustsetenv) {
                    execve(newexec, argv, newenvp);
                } else {
                    execv(newexec, argv);
                }
                execv(newexec, argv);
                JLI_ReportErrorMessageSys(JRE_ERROR4, newexec);
    
            }
            exit(1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264

    GetJREPath方法:

    java_md_solinux.c

    static jboolean
    GetJREPath(char *path, jint pathsize, const char * arch, jboolean speculative)
    {
        char libjava[MAXPATHLEN];
    
        if (GetApplicationHome(path, pathsize)) {  // 读取到路径
            /* Is JRE co-located with the application? */
            JLI_Snprintf(libjava, sizeof(libjava), "%s/lib/%s/" JAVA_DLL, path, arch);
            if (access(libjava, F_OK) == 0) {
                JLI_TraceLauncher("JRE path is %s\n", path);
                return JNI_TRUE;
            }
    
            /* Does the app ship a private JRE in /jre directory? */
            JLI_Snprintf(libjava, sizeof(libjava), "%s/jre/lib/%s/" JAVA_DLL, path, arch);
            if (access(libjava, F_OK) == 0) {
                JLI_StrCat(path, "/jre");
                JLI_TraceLauncher("JRE path is %s\n", path);
                return JNI_TRUE;
            }
        }
    
        if (!speculative)
          JLI_ReportErrorMessage(JRE_ERROR8 JAVA_DLL);
        return JNI_FALSE;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    也即,最终要找到 libjvm.so文件(windows下要找到 jvm.dll)

    要去操作jvm,那么就需要有函数指针去操作jvm:

    在这里插入图片描述

    而这个结构体是在加载jvm的时候使用到了:

    在这里插入图片描述

    jboolean
    LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
    {
        void *libjvm;
    
        JLI_TraceLauncher("JVM path is %s\n", jvmpath);
    
        // 打开动态链接库 
        libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
        if (libjvm == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR1, __LINE__);
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
        }
    
        ifn->CreateJavaVM = (CreateJavaVM_t)
            dlsym(libjvm, "JNI_CreateJavaVM");
        if (ifn->CreateJavaVM == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
        }
    
        ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
            dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
        if (ifn->GetDefaultJavaVMInitArgs == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
        }
    
        ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
            dlsym(libjvm, "JNI_GetCreatedJavaVMs");
        if (ifn->GetCreatedJavaVMs == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
            return JNI_FALSE;
        }
    
        return JNI_TRUE;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    加载结束后,就开始初始化jvm了

    也就是执行JVMInit方法

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    OS欲启动java进程,就需要将libjvm动态链接库里的数据加载出来,并且我们希望创建出来的Java线程要能够可控(调整参数,创建新的线程,不使用原来的线程,然后用新创建出来的线程进行处理),因此我们会在原来的线程上使用pthread_create创建出新的真正用于Java的主线程

    在这里插入图片描述

    在JavaMain的实现体中,有如下代码

    在这里插入图片描述

    也即需要去看InitializeJVM方法具体是如何初始化jvm的

    在这里插入图片描述

    其中的ifn->CreateJavaVM(pvm, (void **)penv, &args);则是将jvm启动起来了

    在这里插入图片描述

    malloc 占用c堆内存 开辟 Java堆内存

    所以: Java堆内内存:Xmx参数指定的malloc开辟的jvm的自己工作空间

    ​ Java堆外内存:原来的c堆内存

    JVM通过传递给原生C一个锚点,通过该锚点向堆内存中读写数据,并调用libjvm中的函数指针,进而不需要再次通过dlsym进行符号解析

    在这里插入图片描述

    hotspot想要加载字节码,就需要用到类加载器,而类加载器又是需要通过hotspot进行加载的,所以告诉我们需要引入一个东西来帮忙加载类加载器,而这个东西就是类库

    由于任何一门高级语言都会有自己的库函数以简化开发,同理,Java语言也应有自己的库函数,而对于Java语言来讲,其自己本身就是一个库函数

    而对于Java来讲,所有的类都需要类加载器进行加载,上面说了Java本身是一个库函数也即类库,那么Java语言本身也就该通过类加载器进行加载

    那么如果只有一个类加载器的话,我可以通过这种方式进行操作

    在这里插入图片描述

    因此我们需要有一种保护机制,来使得用户的应用中写的库函数不会将原来的系统库函数替换掉

    于是为了解决这个问题,需要有多个类加载器,在Java中的类加载器使用了层级结构

    在这里插入图片描述

    No.1只加载系统库函数,而应用程序只能通过No.3的类加载器去加载,这样就防止了应用程序将系统库函数覆盖

    也就是只需要让父类加载器永远优先于子类加载器加载即可

    当我创建多个业务类的类加载器,只需要共享同一个顶级的父类加载器

    在这里插入图片描述

    将类的加载交由父类优先去加载,如果父类无法加载再向下传递,这便是双亲委派机制。

    每一个类加载器都是通过哈希表的形式来映射类的,如果当类一样,方法名也一样的时候,如何加载?建立多个类加载器啊

    而这个key-value的格式就是 SystemDictionary 其中 key就是代表类加载器,value就是应该要加载的类

    对于SystemDictionary的描述为:

    The system dictionary stores all loaded classes and maps:
    [class name,class loader] -> class i.e. [Symbol* ,oop] -> Klass*

    Classes are loaded lazily. The default VM class loader is
    represented as NULL.

    The underlying data structure is an open hash table with a fixed number
    of buckets. During loading the loader object is locked, (for the VM loader
    a private lock object is used). Class loading can thus be done concurrently,
    but only by different loaders.

    During loading a placeholder (name, loader) is temporarily placed in
    a side data structure, and is used to detect ClassCircularityErrors
    and to perform verification during GC. A GC can occur in the midst
    of class loading, as we call out to Java, have to take locks, etc.

    When class loading is finished, a new entry is added to the system
    dictionary and the place holder is removed. Note that the protection
    domain field of the system dictionary has not yet been filled in when
    the “real” system dictionary entry is created.

    Clients of this class who are interested in finding if a class has
    been completely loaded – not classes in the process of being loaded –
    can read the SystemDictionary unlocked. This is safe because

    - entries are only deleted at safepoints
      - readers cannot come to a safepoint while actively examining
          an entry  (an entry cannot be deleted from under a reader)
      - entries must be fully formed before they are available to concurrent
          readers (we must ensure write ordering)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Note that placeholders are deleted at any time, as they are removed
    when a class is completely loaded. Therefore, readers as well as writers
    of placeholders must hold the SystemDictionary_lock.

    第一句话我们就知道了 SystemDictionary存储了所有加载的类和映射信息

    [类名,类加载器] -> class 也即 [Symbol ,oop] -> Klass**

    使用以上的两种方式: 层级结构保证了安全性,哈希表结构保证了隔离性

    而上面的这一套机制需要有一个东西来承载实现,也就是要通过类库来加载这个机制

    因此我们可以在JavaMain方法中找到

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    两大核心文件jvm.cpp和jni.cpp的差异:

    jni.cpp (Java Native Interface)用于在Java层面调C语言,也就是Java与C/C++语言沟通的桥梁

    jvm.cpp java内部的功能函数,可以通过符号调用 内建核心函数

    所以我们在看 findBootClass函数的时候,也就是要找 JVM_FindClassFromBootLoader的函数调用,所以需要在jvm.cpp中查找

    在这里插入图片描述

    据以上代码我们可以看到,并没有BootLoader对象

    也就是说,在于hotspot中的BootLoader实际上是不存在的,这是一个理论上的类加载器,而在c++层面想要加载程序并不一定非得要用到加载器类,而是可以通过c++代码的方式加载类

    jvm是c++写的,那我直接在类库中写c++代码加载的Java类算不算ClassLoader?因此无法通过Java层面getBootLoader,也即BootLoader类不管是在Java层面还在C++层面都没有这个类的实现

    至此,我们加载了Java中的LauncherHelper这个类

    而我们知道c++是如何创建一个类的,以及Java创建类的过程

    所以我们可以推断Java对象的创建可以与c++类比

    在这里插入图片描述

    在c++的类代码中,static是当前文件共享,而Java的static是在类的空间中,static修饰的不属于对象,而是所有对象共享

    上图中,Java由于静态变量隶属于类,所以需要类的构造器,而成员变量属于对象,则需要对象构造器

    我们在写Java的时候经常会遇到static{}在大括号中赋值的写法(静态代码块),这就是Java的类构造器,而平常写的构造函数则是对象的构造器

    因此:类构造器打包static{}和静态变量赋值语句,按顺序排列 -> 由JVM对clinit类构造器的定义

    public class A {
        static{
        }
        public static void main(String[] args) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    Classfile /H:/source_code/openjdk8_debug/target/classes/hahaha/xjk/xx/A.class
      Last modified 2022-10-6; size 424 bytes
      MD5 checksum 28df4b7f385a582580cb963ff4a2441c
      Compiled from "A.java"
    public class hahaha.xjk.xx.A
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #3.#18         // java/lang/Object."":()V
       #2 = Class              #19            // hahaha/xjk/xx/A
       #3 = Class              #20            // java/lang/Object
       #4 = Utf8               <init>
       #5 = Utf8               ()V
       #6 = Utf8               Code
       #7 = Utf8               LineNumberTable
       #8 = Utf8               LocalVariableTable
       #9 = Utf8               this
      #10 = Utf8               Lhahaha/xjk/xx/A;
      #11 = Utf8               main
      #12 = Utf8               ([Ljava/lang/String;)V
      #13 = Utf8               args
      #14 = Utf8               [Ljava/lang/String;
      #15 = Utf8               <clinit>
      #16 = Utf8               SourceFile
      #17 = Utf8               A.java
      #18 = NameAndType        #4:#5          // "":()V
      #19 = Utf8               hahaha/xjk/xx/A
      #20 = Utf8               java/lang/Object
    {
      public hahaha.xjk.xx.A();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."":()V
             4: return
          LineNumberTable:
            line 8: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lhahaha/xjk/xx/A;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=0, locals=1, args_size=1
             0: return
          LineNumberTable:
            line 17: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       1     0  args   [Ljava/lang/String;
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=0, locals=0, args_size=0
             0: return
          LineNumberTable:
            line 12: 0
    }
    SourceFile: "A.java"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    通过对字节码的分析,发现并没有clinit方法,因此可以推测clinit方法是在Java虚拟机中实现并执行的

    在前面的hotspot中看到了加载了一个LauncherHelper类,这个Java类中没有静态代码块,并且也没有这个类的main方法

    如何执行的这个类呢?

    在这里插入图片描述

    也就是执行了LaucherHelper中的checkAndLoadMain方法

    在JavaMain中先执行了LoadMainClass,也就是加载并调用了LauncherHelper中的checkAndLoadMain方法

    然后再通过函数指针调main方法

    在这里插入图片描述

    在这里插入图片描述

    执行java的main方法

    综上所述,

    LauncherHelper类作为Java层面的被加载的第一个类,提供了加载字节码和其他功能函数

    JNIEnv提供了C与JVM的交互方式

    而bootLoader直接用c++代码编写,加载LauncherHelper,打破了第一个类加载器需要被加载的问题

    public static Class<?> checkAndLoadMain(boolean printToStderr,
                                            int mode,
                                            String what) {
        initOutput(printToStderr);
        // get the class name
        String cn = null;
        switch (mode) {
            case LM_CLASS:
                cn = what;
                break;
            case LM_JAR:
                cn = getMainClassFromJar(what);
                break;
            default:
                // should never happen
                throw new InternalError("" + mode + ": Unknown launch mode");
        }
        cn = cn.replace('/', '.');
        Class<?> mainClass = null;
        try {
            mainClass = scloader.loadClass(cn);
        } catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
            if (System.getProperty("os.name", "").contains("OS X")
                && Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {
                try {
                    // On Mac OS X since all names with diacretic symbols are given as decomposed it
                    // is possible that main class name comes incorrectly from the command line
                    // and we have to re-compose it
                    mainClass = scloader.loadClass(Normalizer.normalize(cn, Normalizer.Form.NFC));
                } catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {
                    abort(cnfe, "java.launcher.cls.error1", cn);
                }
            } else {
                abort(cnfe, "java.launcher.cls.error1", cn);
            }
        }
        // set to mainClass
        appClass = mainClass;
    
        /*
         * Check if FXHelper can launch it using the FX launcher. In an FX app,
         * the main class may or may not have a main method, so do this before
         * validating the main class.
         */
        if (mainClass.equals(FXHelper.class) ||
                FXHelper.doesExtendFXApplication(mainClass)) {
            // Will abort() if there are problems with the FX runtime
            FXHelper.setFXLaunchParameters(what, mode);
            return FXHelper.class;
        }
    
        validateMainClass(mainClass);
        return mainClass;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    这个时候调用了systemClassLoader

    在这里插入图片描述

    而上面的重点代码便是:

    在这里插入图片描述
    在这里插入图片描述

    那么getLauncher又是如何get的呢?

    private ClassLoader loader;
    public Launcher() {
        ClassLoader extcl;
        extcl = ExtClassLoader.getExtClassLoader();
        loader = AppClassLoader.getAppClassLoader(extcl);
        Thread.currentThread().setContextClassLoader(loader);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    此时可以看到了ExtClassLoader和AppClassLoader

    两个的继承关系如下:

    在这里插入图片描述

    ClassLoader定义了类加载器的顶层共有最基本的信息

    SecureClassLoader引入安全机制,决定哪些类可以加载哪些类不可以被加载

    URLClassLoader提供文件名,通过给定的路径或约定的路径去寻找类

    ExtClassLoader扩展类加载器

    AppClassLoader具体应用类加载器

    那么我们要去研究ClassLoader的最基本的方法,也就是loadClass,因此需要通过ClassLoader这个类去看看他的方法定义。

    在这里插入图片描述

    将父类构造器作为一个成员放入类中,默认为SystemClassLoader

    通过父类去加载,如果父类构造器没有则去通过BootStrap加载(C++本地方法)

    如果都没找到则让子类去实现findClass

    对于我们现在考虑某个类的加载而言,暂时无需关心安全问题,直接跳到URLClassLoader中

    在这里插入图片描述

    替换.成/,并在末尾添加.class 最终通过URLClassPath加载

    而ExtClassLoader应该为AppClassLoader的parent,不通过继承关系获得,而是使用设置值的方式,执行loadClass的时候先让parent去加载

    在这里插入图片描述

    同理,那么我想要自己实现一个类加载器如何做?

    继承 URLClassLoader或者ClassLoader,实现loadClass方法,将AppClassLoader作为parent,并实现自己的findClass方法

    ExtClassLoader

    在这里插入图片描述

    AppClassLoader

    在这里插入图片描述

    而核心文件,如rt.jar包的内容由BootstrapClassLoader(C++代码)直接加载

    这里需要注意的是在Launcher中有一个SystemClassLoader,这个也就是AppClassLoader

    了解了整个加载过程,那么,main函数最终经过这几个步骤便完成了Java main的加载,

    在这里插入图片描述

    调用由虚拟机调用,因此我们需要去研究jvm是如何启动的

    因为jni.cpp是连通Java与c/cpp的桥梁,但凡外部需要与jvm沟通就必须经过jni

    而在java.c中我们看到的只有一个LoadJavaVM方法中有一个JNI_CreatJavaVM

    也就是意味着,这个时候将jni中的Java虚拟机创建并动态链接过来,在invoke查找符号的时候找到的CreateJavaVM

    在这里插入图片描述

    并且在JavaMain中

    在这里插入图片描述

    在这里插入图片描述

    也就是ifn引用的需要通过JNI_XXX去查找在jni.cpp中 如下代码:

    _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) 
        jint result = JNI_ERR;
        result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
        return result;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    因此研究create的过程需要找到JNI下的createVM方法

    在这里插入图片描述

    Java虚拟机是模拟了计算机的所有信息

    既然是虚拟,那么肯定与OS有关联关系,也必然与CPU 指令集相关

    第一步肯定是需要操作系统的相关信息,所有系统初始化第一步必然要读取操作系统的所有信息

    在这里插入图片描述

    // this is called _before_ the most of global arguments have been parsed
    void os::init(void) {
      char dummy;   /* used to get a guess on initial stack address */
    //  first_hrtime = gethrtime();
    
      // With LinuxThreads the JavaMain thread pid (primordial thread)
      // is different than the pid of the java launcher thread.
      // So, on Linux, the launcher thread pid is passed to the VM
      // via the sun.java.launcher.pid property.
      // Use this property instead of getpid() if it was correctly passed.
      // See bug 6351349.
      pid_t java_launcher_pid = (pid_t) Arguments::sun_java_launcher_pid();
    
      _initial_pid = (java_launcher_pid > 0) ? java_launcher_pid : getpid();
    
      clock_tics_per_sec = sysconf(_SC_CLK_TCK);
    
      init_random(1234567);
    
      ThreadCritical::initialize();
    
      Linux::set_page_size(sysconf(_SC_PAGESIZE));
      if (Linux::page_size() == -1) {
        fatal(err_msg("os_linux.cpp: os::init: sysconf failed (%s)",
                      strerror(errno)));
      }
      init_page_sizes((size_t) Linux::page_size());
    
      Linux::initialize_system_info();
    
      // main_thread points to the aboriginal thread
      Linux::_main_thread = pthread_self();
    
      Linux::clock_init();
      initial_time_count = javaTimeNanos();
    
      // pthread_condattr initialization for monotonic clock
      int status;
      pthread_condattr_t* _condattr = os::Linux::condAttr();
      if ((status = pthread_condattr_init(_condattr)) != 0) {
        fatal(err_msg("pthread_condattr_init: %s", strerror(status)));
      }
      // Only set the clock if CLOCK_MONOTONIC is available
      if (Linux::supports_monotonic_clock()) {
        if ((status = pthread_condattr_setclock(_condattr, CLOCK_MONOTONIC)) != 0) {
          if (status == EINVAL) {
            warning("Unable to use monotonic clock with relative timed-waits" \
                    " - changes to the time-of-day clock may have adverse affects");
          } else {
            fatal(err_msg("pthread_condattr_setclock: %s", strerror(status)));
          }
        }
      }
      // else it defaults to CLOCK_REALTIME
    
      pthread_mutex_init(&dl_mutex, NULL);
    
      // If the pagesize of the VM is greater than 8K determine the appropriate
      // number of initial guard pages.  The user can change this with the
      // command line arguments, if needed.
      if (vm_page_size() > (int)Linux::vm_default_page_size()) {
        StackYellowPages = 1;
        StackRedPages = 1;
        StackShadowPages = round_to((StackShadowPages*Linux::vm_default_page_size()), vm_page_size()) / vm_page_size();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    再OS相关初始化完毕后需要处理参数信息

    在这里插入图片描述

    在这里插入图片描述

    由于 在这里插入图片描述
    是将系统的变量注入到全局的system_properties中去

    这个时候是在创建Java虚拟机过程中,所以并没有java的System类,所以需要有C++类来暂时承载system_properties

    在这里插入图片描述
    由这个全局变量保存整个system_property

    而Java的System是如何获取system properties的呢?

    在这里插入图片描述

    而getProperty 的Java代码如下

    在这里插入图片描述

    然后从props里面拿数据,而谁往props里面填入数据的呢?

    肯定是初始化的时候填入的数据嘛

    在这里插入图片描述

    有native关键字修饰,那么就是jni的本地函数

    而这个initProperties是线程启动的时候自动调用

    之前看到的Java和jni之间的交互是需要用到JNIEnv作为中介的

    在这里插入图片描述

    那么Java语言中用native修饰的方法该怎么找到对应的C语言实现呢?

    第1步,找到Java语言native修饰的方法所在的类名,类名.c就是对该方法的实现

    第2步,将类所在的 包名.类名.方法名 中的 .替换成_ ,前面补上Java_

    即可找到对应的本地方法实现

    比如,现在我需要找initProperties的C语言实现方法

    那么就要先找到system.c,然后找到 Java_java_lang_System_initProperties

    如图:

    在这里插入图片描述

    原因是:

    jvm需要做如下工作

    1、dlopen打开动态链接库

    2、按照规约 dlsym ,通过规约符号找到符号函数地址

    3、保存链接库的引用(后续可以使用)

    4、调用获取到的函数地址

    那么为什么要定义这个规约呢?

    如果不定义规约,你按照你的方式去找,我按照我的定义去写,最终对应不上,那你虚拟机也就找不到对应的Java方法

    因此我们需要回过头来看看Java_java_lang_System_initProperties方法里面具体干了什么事

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    对于GetJavaProperties函数返回了一个内部用于保存所有properties数据的结构体

    再通过JNIEnv调用Java中的put,remove,getProperty等方法,也即通过调用到了Java中的Properties对象

    Java层面:在initProperties中需要传入Properties对象

    那么在执行initProperties之前需要先存在有Properties对象,而这个对象需要通过JNIEnv调用Java的Properties对象属性获取

    于是在c层面, 使用了(*env)->GetMethodID调用Properties,通过c代码可以知道,调用了Properties的 put,remove,getProperty,找到对应方法的地址

    通过Java代码可以知道Properties继承自Hashtable,所以必定有put remove方法,而getProperty方法按照JNIEnv写法应该是获取这个方法的函数地址:在这里插入图片描述

    那么这个时候就需要知道,到底是谁,在哪儿将property传入进去了呢?

    之前我们看到了在Threads::createVM方法中执行了在这里插入图片描述
    方法,而我们需要思考的是,init_system_properties确实是设置了一个全局的系统配置,也即在这里插入图片描述

    那么谁调用了 _system_properties呢?在这里插入图片描述

    谁调用了system_properties呢?我们可以找到jvm.cpp中有对此的调用

    在这里插入图片描述

    这个for循环的调用来自于在这里插入图片描述

    所以也就是当C语言调用JVM_InitProperties方法的时候会进入到这个地方

    谁调用了JVM_InitProperties方法?返回去看Java_java_lang_System_initProperties方法,这其中就调用了JVM_InitProperties方法,

    在这里插入图片描述

    回到createVM的流程中,

    在这里插入图片描述

    这段处理,都是初始化系统属性

    将很多系统的属性插入到了Arguments中

    那么下一步应该是需要转换一些参数解析处理了吧

    如下,将前面设置好的系统属性转换成jvm的系统参数进行解析

    在这里插入图片描述

    然后需要在每一个hotspot模块初始化之前需要初始化大页(如果页小,则页表项多,所以需要分配一部分大页内存)

    在这里插入图片描述

    经过其他处理,进行OS初始化第二阶段

    在这里插入图片描述

    os::init_2方法中存在一个polling page,而前面的page内存经过设置,这里使用mmap进行内存分配

    思考一个问题:如何将一系列Java线程停下来?

    Java线程通过检测某个标志位,当出现某个标志的时候线程自己停下来即可。这个标志位如何做呢?

    设置一个不可访问的页,当线程访问到这个页就报错,进而自动停下来。

    在什么时候检测信号标志位呢?在线程安全的时候去检测嘛,也即 线程安全点(safe point)

    在这里插入图片描述

    在这里插入图片描述

    完成init_2之后,需要再次调整参数存储

    在这里插入图片描述

    进行TLS初始化

    在这里插入图片描述

    在这里插入图片描述

    之后会进行一些处理,之后,createVM会执行到此处:

    在这里插入图片描述

    _thread_list 实现Java线程列表,所有创建的Java线程均保存在这个列表中

    _number_of_threads 线程列表中线程的个数

    _number_of_non_daemon_threads 线程列表中非守护线程的个数,当这里面的线程个数为0时,主线程结束,所有线程都结束

    在堆中初始化全局数据结构和创建系统类(主函数代码)

    在这里插入图片描述

    在这里插入图片描述

    继续上面的,createVM的过程:

    在这里插入图片描述

    在这里插入图片描述

    这个main_thread也就是大家所理解的Java主线程

    如果没有JavaThread,如何表达当前Java线程所处的状态信息?因为这个状态不属于TCB,也不属于PCB,用于表示当前线程正在执行虚拟机的代码

    而main_thread表示当前线程的执行体

    所以这个就是用于承载 在jvm层面上对于Java线程对象的信息

    下面大概讲讲JavaThread创建的过程

    根据jni的规范,我们知道在Java中 new Thread().start()的jni应该是在Thread.c中

    在这里插入图片描述

    而我们知道JVM_开头的内建核心函数是在jvm.cpp中

    在这里插入图片描述

    其中有一行代码如下

    在这里插入图片描述

    通过函数指针执行了thread_entry方法,而thread_entry干了什么事呢?

    在这里插入图片描述

    其中可以看到 vmSymbols::run_method_name(),说明,反向调用了run方法

    set_thread_state()方法传入的是线程状态,因此我们需要去看看Java线程真实的状态是哪些?

    在这里插入图片描述

    这个JavaThreadState是对整个Java线程所有的状态

    除此之外,Java在执行字节码的时候执行的状态应该是:

    在这里插入图片描述

    继续create_vm

    在这里插入图片描述

    初始化全局模块的信息

    在这里插入图片描述

    jint init_globals() {
      HandleMark hm;
      management_init();  // 初始化管理模块
      bytecodes_init();  // 初始化字节码信息
      classLoader_init();  // 初始化类加载器
      codeCache_init();  // 初始化代码缓存
      VM_Version_init();  // 初始化VM版本信息
      os_init_globals();  // 初始化OS全局信息
      stubRoutines_init1();  // 代码路由第一步初始化
      jint status = universe_init();  // 初始化GC相关信息(依赖于codeCache_init和stubRoutines_init1和metaspace_init)
    
      if (status != JNI_OK) // 如果初始化失败,返回状态信息
        return status;
    
      interpreter_init();  // 初始化解释器(在所有方法加载之前)
      invocationCounter_init();  // 初始化调用程序计数器(在所有方法加载之前)
      marksweep_init();  // 初始化标记清除垃圾回收相关信息
      accessFlags_init();  // 初始化访问修饰符模块
      templateTable_init(); // 初始化字节码模板表
      InterfaceSupport_init();  // 初始化接口支持模块
      SharedRuntime::generate_stubs();  // 初始化代码调用stub
      universe2_init();  // 对GC相关信息进行二次初始化(依赖于codeCache_init和stubRoutines_init1)
      referenceProcessor_init();  // 初始化引用处理
      jni_handles_init();  // 初始化JNI回调模块
    #if INCLUDE_VM_STRUCTS
      vmStructs_init();  // vm结构体初始化
    #endif // INCLUDE_VM_STRUCTS
    
      vtableStubs_init();   // 初始化虚拟方法表模块
      InlineCacheBuffer_init();  // 初始化内联代码缓冲模块
      compilerOracle_init();  // 初始化orcale设置的编译器
      compilationPolicy_init();  // 初始化编译策略模块
      compileBroker_init();  // 初始化编译管理器模块
      VMRegImpl::set_regName();
    
      if (!universe_post_init()) {  // 对universe也即gc相关进行钩子回调
        return JNI_ERR;
      }
      javaClasses_init();   // 初始化java类信息 (必须在虚拟方法表初始化之后)
      stubRoutines_init2(); // 对代码路由二次初始化(注意: StubRoutines需要第二阶段的初始化)
    
      // 所有由VM_Version_init和os::init_2调整的标志都已经设置,所以现在转储标志。
      if (PrintFlagsFinal) { // 如果设置了PrintFlagsFinal为true,那么在这里打印所有参数信息
        CommandLineFlags::printFlags(tty, false);
      }
    
      return JNI_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    上面的 init_globals方法中的bytecode_init就是将Java字节码进行初始化

    定义了203个字节码信息

    所以每个字节码定义的长度都是32位或者64位

    可以对应着和RISC学习

    每个字节码长度一致

     enum Code {
        _illegal              =  -1,
    
        // Java bytecodes
        _nop                  =   0, // 0x00
        _aconst_null          =   1, // 0x01
        _iconst_m1            =   2, // 0x02
        _iconst_0             =   3, // 0x03
        _iconst_1             =   4, // 0x04
        _iconst_2             =   5, // 0x05
        _iconst_3             =   6, // 0x06
        _iconst_4             =   7, // 0x07
        _iconst_5             =   8, // 0x08
        _lconst_0             =   9, // 0x09
        _lconst_1             =  10, // 0x0a
        _fconst_0             =  11, // 0x0b
        _fconst_1             =  12, // 0x0c
        _fconst_2             =  13, // 0x0d
        _dconst_0             =  14, // 0x0e
        _dconst_1             =  15, // 0x0f
        _bipush               =  16, // 0x10
        _sipush               =  17, // 0x11
        _ldc                  =  18, // 0x12
        _ldc_w                =  19, // 0x13
        _ldc2_w               =  20, // 0x14
        _iload                =  21, // 0x15
        _lload                =  22, // 0x16
        _fload                =  23, // 0x17
        _dload                =  24, // 0x18
        _aload                =  25, // 0x19
        _iload_0              =  26, // 0x1a
        _iload_1              =  27, // 0x1b
        _iload_2              =  28, // 0x1c
        _iload_3              =  29, // 0x1d
        _lload_0              =  30, // 0x1e
        _lload_1              =  31, // 0x1f
        _lload_2              =  32, // 0x20
        _lload_3              =  33, // 0x21
        _fload_0              =  34, // 0x22
        _fload_1              =  35, // 0x23
        _fload_2              =  36, // 0x24
        _fload_3              =  37, // 0x25
        _dload_0              =  38, // 0x26
        _dload_1              =  39, // 0x27
        _dload_2              =  40, // 0x28
        _dload_3              =  41, // 0x29
        _aload_0              =  42, // 0x2a
        _aload_1              =  43, // 0x2b
        _aload_2              =  44, // 0x2c
        _aload_3              =  45, // 0x2d
        _iaload               =  46, // 0x2e
        _laload               =  47, // 0x2f
        _faload               =  48, // 0x30
        _daload               =  49, // 0x31
        _aaload               =  50, // 0x32
        _baload               =  51, // 0x33
        _caload               =  52, // 0x34
        _saload               =  53, // 0x35
        _istore               =  54, // 0x36
        _lstore               =  55, // 0x37
        _fstore               =  56, // 0x38
        _dstore               =  57, // 0x39
        _astore               =  58, // 0x3a
        _istore_0             =  59, // 0x3b
        _istore_1             =  60, // 0x3c
        _istore_2             =  61, // 0x3d
        _istore_3             =  62, // 0x3e
        _lstore_0             =  63, // 0x3f
        _lstore_1             =  64, // 0x40
        _lstore_2             =  65, // 0x41
        _lstore_3             =  66, // 0x42
        _fstore_0             =  67, // 0x43
        _fstore_1             =  68, // 0x44
        _fstore_2             =  69, // 0x45
        _fstore_3             =  70, // 0x46
        _dstore_0             =  71, // 0x47
        _dstore_1             =  72, // 0x48
        _dstore_2             =  73, // 0x49
        _dstore_3             =  74, // 0x4a
        _astore_0             =  75, // 0x4b
        _astore_1             =  76, // 0x4c
        _astore_2             =  77, // 0x4d
        _astore_3             =  78, // 0x4e
        _iastore              =  79, // 0x4f
        _lastore              =  80, // 0x50
        _fastore              =  81, // 0x51
        _dastore              =  82, // 0x52
        _aastore              =  83, // 0x53
        _bastore              =  84, // 0x54
        _castore              =  85, // 0x55
        _sastore              =  86, // 0x56
        _pop                  =  87, // 0x57
        _pop2                 =  88, // 0x58
        _dup                  =  89, // 0x59
        _dup_x1               =  90, // 0x5a
        _dup_x2               =  91, // 0x5b
        _dup2                 =  92, // 0x5c
        _dup2_x1              =  93, // 0x5d
        _dup2_x2              =  94, // 0x5e
        _swap                 =  95, // 0x5f
        _iadd                 =  96, // 0x60
        _ladd                 =  97, // 0x61
        _fadd                 =  98, // 0x62
        _dadd                 =  99, // 0x63
        _isub                 = 100, // 0x64
        _lsub                 = 101, // 0x65
        _fsub                 = 102, // 0x66
        _dsub                 = 103, // 0x67
        _imul                 = 104, // 0x68
        _lmul                 = 105, // 0x69
        _fmul                 = 106, // 0x6a
        _dmul                 = 107, // 0x6b
        _idiv                 = 108, // 0x6c
        _ldiv                 = 109, // 0x6d
        _fdiv                 = 110, // 0x6e
        _ddiv                 = 111, // 0x6f
        _irem                 = 112, // 0x70
        _lrem                 = 113, // 0x71
        _frem                 = 114, // 0x72
        _drem                 = 115, // 0x73
        _ineg                 = 116, // 0x74
        _lneg                 = 117, // 0x75
        _fneg                 = 118, // 0x76
        _dneg                 = 119, // 0x77
        _ishl                 = 120, // 0x78
        _lshl                 = 121, // 0x79
        _ishr                 = 122, // 0x7a
        _lshr                 = 123, // 0x7b
        _iushr                = 124, // 0x7c
        _lushr                = 125, // 0x7d
        _iand                 = 126, // 0x7e
        _land                 = 127, // 0x7f
        _ior                  = 128, // 0x80
        _lor                  = 129, // 0x81
        _ixor                 = 130, // 0x82
        _lxor                 = 131, // 0x83
        _iinc                 = 132, // 0x84
        _i2l                  = 133, // 0x85
        _i2f                  = 134, // 0x86
        _i2d                  = 135, // 0x87
        _l2i                  = 136, // 0x88
        _l2f                  = 137, // 0x89
        _l2d                  = 138, // 0x8a
        _f2i                  = 139, // 0x8b
        _f2l                  = 140, // 0x8c
        _f2d                  = 141, // 0x8d
        _d2i                  = 142, // 0x8e
        _d2l                  = 143, // 0x8f
        _d2f                  = 144, // 0x90
        _i2b                  = 145, // 0x91
        _i2c                  = 146, // 0x92
        _i2s                  = 147, // 0x93
        _lcmp                 = 148, // 0x94
        _fcmpl                = 149, // 0x95
        _fcmpg                = 150, // 0x96
        _dcmpl                = 151, // 0x97
        _dcmpg                = 152, // 0x98
        _ifeq                 = 153, // 0x99
        _ifne                 = 154, // 0x9a
        _iflt                 = 155, // 0x9b
        _ifge                 = 156, // 0x9c
        _ifgt                 = 157, // 0x9d
        _ifle                 = 158, // 0x9e
        _if_icmpeq            = 159, // 0x9f
        _if_icmpne            = 160, // 0xa0
        _if_icmplt            = 161, // 0xa1
        _if_icmpge            = 162, // 0xa2
        _if_icmpgt            = 163, // 0xa3
        _if_icmple            = 164, // 0xa4
        _if_acmpeq            = 165, // 0xa5
        _if_acmpne            = 166, // 0xa6
        _goto                 = 167, // 0xa7
        _jsr                  = 168, // 0xa8
        _ret                  = 169, // 0xa9
        _tableswitch          = 170, // 0xaa
        _lookupswitch         = 171, // 0xab
        _ireturn              = 172, // 0xac
        _lreturn              = 173, // 0xad
        _freturn              = 174, // 0xae
        _dreturn              = 175, // 0xaf
        _areturn              = 176, // 0xb0
        _return               = 177, // 0xb1
        _getstatic            = 178, // 0xb2
        _putstatic            = 179, // 0xb3
        _getfield             = 180, // 0xb4
        _putfield             = 181, // 0xb5
        _invokevirtual        = 182, // 0xb6
        _invokespecial        = 183, // 0xb7
        _invokestatic         = 184, // 0xb8
        _invokeinterface      = 185, // 0xb9
        _invokedynamic        = 186, // 0xba     // if EnableInvokeDynamic
        _new                  = 187, // 0xbb
        _newarray             = 188, // 0xbc
        _anewarray            = 189, // 0xbd
        _arraylength          = 190, // 0xbe
        _athrow               = 191, // 0xbf
        _checkcast            = 192, // 0xc0
        _instanceof           = 193, // 0xc1
        _monitorenter         = 194, // 0xc2
        _monitorexit          = 195, // 0xc3
        _wide                 = 196, // 0xc4
        _multianewarray       = 197, // 0xc5
        _ifnull               = 198, // 0xc6
        _ifnonnull            = 199, // 0xc7
        _goto_w               = 200, // 0xc8
        _jsr_w                = 201, // 0xc9
        _breakpoint           = 202, // 0xca
    
        number_of_java_codes,
    
        // JVM bytecodes
        _fast_agetfield       = number_of_java_codes,
        _fast_bgetfield       ,
        _fast_cgetfield       ,
        _fast_dgetfield       ,
        _fast_fgetfield       ,
        _fast_igetfield       ,
        _fast_lgetfield       ,
        _fast_sgetfield       ,
    
        _fast_aputfield       ,
        _fast_bputfield       ,
        _fast_cputfield       ,
        _fast_dputfield       ,
        _fast_fputfield       ,
        _fast_iputfield       ,
        _fast_lputfield       ,
        _fast_sputfield       ,
    
        _fast_aload_0         ,
        _fast_iaccess_0       ,
        _fast_aaccess_0       ,
        _fast_faccess_0       ,
    
        _fast_iload           ,
        _fast_iload2          ,
        _fast_icaload         ,
    
        _fast_invokevfinal    ,
        _fast_linearswitch    ,
        _fast_binaryswitch    ,
    
        // special handling of oop constants:
        _fast_aldc            ,
        _fast_aldc_w          ,
    
        _return_register_finalizer    ,
    
        // special handling of signature-polymorphic methods:
        _invokehandle         ,
    
        _shouldnotreachhere,      // For debugging
    
        // Platform specific JVM bytecodes
    #ifdef TARGET_ARCH_x86
    # include "bytecodes_x86.hpp"
    #endif
    #ifdef TARGET_ARCH_sparc
    # include "bytecodes_sparc.hpp"
    #endif
    #ifdef TARGET_ARCH_zero
    # include "bytecodes_zero.hpp"
    #endif
    #ifdef TARGET_ARCH_arm
    # include "bytecodes_arm.hpp"
    #endif
    #ifdef TARGET_ARCH_ppc
    # include "bytecodes_ppc.hpp"
    #endif
    
    
        number_of_codes
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273

    由于字节码和RISC指令集很像,可以类比学习

    那么需要思考一个问题

    在这里插入图片描述

    这个字节码中,为什么没有寄存器和内存呢???

    那这样就引来了另外一个问题,Java虚拟机到底虚拟了什么?

    必定是虚拟了真实计算机的虚拟环境

    根据虚拟机的信息,我们知道如下信息:

    字节码根本不知道会执行在哪个平台的cpu上,所以寄存器无法定义,因此需要虚拟寄存器

    于是,Java虚拟机使用栈来模拟寄存器来操作(ALU)即可

    想要用栈来虚拟寄存器的操作,那么虚拟的寄存器是哪些呢?也就是说,哪个地方是虚拟的哪一种寄存器呢?

    所以需要用到一个局部变量表来虚拟寄存器,通过局部变量表告诉虚拟机哪些寄存器在哪儿

    综上,想要Java虚拟机来模拟寄存器操作

    第一步,使用局部变量表(一个数组)来模拟寄存器

    第二步,使用栈来模拟对寄存器的操作

    在这里插入图片描述

    在这里插入图片描述

    这种写法便是c++解释器对字节码的解释

    在这里插入图片描述

    一个循环,对每一个字节码进行判断解释

    而模板解释器与C++解释器不同

    虽然Java与平台无关,但是执行字节码的主体是虚拟机,所以虚拟机就与平台有关了

    也就是说jvm与CPU平台和OS高度耦合,并且知道自己是运行在哪个平台上,所以可以直接使用平台相关寄存器来执行字节码以提升性能

    所以在templateTable.cpp中就定义了每个字节码对应的汇编指令操作

    在这里插入图片描述

    例如_iconst_1对应为 iconst方法,参数为1

    由于此时模板解释器与平台挂钩,所以我们看 x86_64处理器

    在这里插入图片描述

    将宏展开后如下

    void TemplateTable::iconst(int value) {
      transition(vtos, itos);
      if (value == 0) {
        _masm-> xorl(rax, rax);
      } else {
        _masm-> movl(rax, value);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    显而易见,使用了movl / xorl操作 rax寄存器

    前面看到的Bytecodes的def方法是干了什么事呢?

    在这里插入图片描述

    也就是将各种的code,name,format等等信息保存到几个数组中

    回到init_globals方法中

    在这里插入图片描述

    初始化一系列counter用于之后的性能分析器

    在这里插入图片描述

    其中,在initialize方法中存在这个方法

    load_zip_library();也即,加载了zip库

    那么肯定会调用动态链接库,也肯定会执行dlopen方法

    在这里插入图片描述

    在这里插入图片描述

    dll_load方法中执行:

    在这里插入图片描述

    在这里插入图片描述

    通过上面的代码知道,除了加载了libjvm.so这个虚拟机本身动态链接库之外,还需要加载其他的动态链接库

    在这里插入图片描述

    从编译的jdk代码中可以知道需要引入libjava.so等各种动态链接库

    在这里插入图片描述

    继续init_globals方法

    在这里插入图片描述

    所谓代码缓存,就是jit在执行的时候用于动态生成的代码片段

    在这里插入图片描述

    其中,stub就是一个函数指针,指向一个用来执行的代码段,可以是c++代码也可以是汇编指令段,有输入有输出

    在这里插入图片描述

    在这里插入图片描述

    ReservedSpace调用了 mmap映射一大段空间,都是虚拟地址,也就是有页表但是没有页表项

    在这里插入图片描述

    在这里插入图片描述

    initialize方法中调用了os的 预留内存的方法

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    这个mmap分配的是虚拟内存,没有页表项

    回到init_globals中

    执行到stubRoutines_init1();

    什么是stub在前面讲过,那么一组stub就是stubRouting

    在这里插入图片描述

    StubRoutines为 被编译后的汇编代码段和运行时系统提供入口点。

    特定平台的入口点被定义在特定的平台的类中

    Class 组合:

    在这里插入图片描述

    Note 1: The important thing is a clean decoupling between stub
    entry points (interfacing to the whole vm; i.e., 1-to-n
    relationship) and stub generators (interfacing only to
    the entry points implementation; i.e., 1-to-1 relationship).
    This significantly simplifies changes in the generator
    structure since the rest of the vm is not affected.

    Note 2: stubGenerator_.cpp contains a minimal portion of
    machine-independent code; namely the generator calls of
    the generator functions that are used platform-independently.
    However, it comes with the advantage of having a 1-file
    implementation of the generator. It should be fairly easy
    to change, should it become a problem later.

    Scheme for adding a new entry point:

    1. determine if it’s a platform-dependent or independent entry point
      a) if platform independent: make subsequent changes in the independent files
      b) if platform dependent: make subsequent changes in the dependent files
      2. add a private instance variable holding the entry point address
      3. add a public accessor function to the instance variable
      4. implement the corresponding generator function in the platform-dependent
      stubGenerator_.cpp file and call the function in generate_all() of that file

    在这里插入图片描述

    在这里插入图片描述

    CodeBlob用来保存二进制代码

    在这里插入图片描述

    而 StubRoutines就是如下部分,那么谁给这些属性赋值的呢?

    在这里插入图片描述

    StubGenerator_generate(&buffer, false); 这行代码实现对上面属性的赋值操作

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    之后的函数调用都会走到这个地方,尤其是invoke的时候

    在这里插入图片描述

    这里的函数指针其实就是一个用来存放地址的东西

    以下代码是stubGenerator_zero.cpp中

    在这里插入图片描述

    也即零汇编代码(所谓零汇编,就是这个部分是由c++代码实现而不是通过直接生成汇编指令实现),

    因此需要使用ShouldNotCallThisStub()方法,也即将对于需要使用汇编的stub设置为不可达的地址,一旦执行则报错

    在init_globals方法中我们可以看到subroutines的初始化方法有两个部分

    分别是在这里插入图片描述
    在这里插入图片描述

    因为第二部分需要其他部分先初始化,因此将stubRoutines的初始化分为了两个部分

    回到init_globals

    在这里插入图片描述

    jint universe_init() {
    
        jint status = Universe::initialize_heap(); //初始化堆内存
        if (status != JNI_OK) {
            return status;
        }
    
        Metaspace::global_initialize(); // 初始化元数据空间
    
        // Create memory for metadata.  Must be after initializing heap for
        // DumpSharedSpaces.
        ClassLoaderData::init_null_class_loader_data(); //初始化类加载器的使用数据
    
        SymbolTable::create_table(); //创建初始化符号表
        StringTable::create_table(); // 创建初始化字符串表
        ClassLoader::create_package_info_table(); // 创建初始化类加载器的包信息表
    
        return JNI_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对于initialize_heap方法

    jint Universe::initialize_heap() {
    
        GenCollectorPolicy *gc_policy;
    
        gc_policy = new MarkSweepPolicy();
        gc_policy->initialize_all();  // 初始化GC算法	
    
        Universe::_collectedHeap = new GenCollectedHeap(gc_policy);
    
        jint status = Universe::heap()->initialize(); // 初始化内存
        if (status != JNI_OK) {
            return status;
        }
    
        return JNI_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    所以,两个分别管理垃圾回收和内存分配的问题

    gc_policy->initialize_all(); 方法就是如下

    在这里插入图片描述

    而GenCollectorPolicy继承于CollectorPolicy

    也就是分代GC策略继承于GC策略

    内存整理:适用于对象小,而且对象存活相对较少

    那么也就是说,整理算法怕 大对象,对象多

    因此,将大对象和存活次数很多直接存放老年代,而一般这种整理算法都是采用复制算法

    新生代和老年代的区别?

    新生代用于存放存活较少的,空间较小的对象,便于内存的复制整理

    老年代存放空间占用大的,存活率很高的对象,允许存在碎片问题

    新生代对象什么时候进入老年代?

    由于有些对象一直存在,不能让它在新生代一直来回复制,所以需要让它达到一定次数之后扔到老年代中,于是需要引入计数GC复制次数(对象年龄),当年龄达到阈值时存放老年代

    新生代为什么会需要有S1(from)和S2(to)两个空间呢?

    因为对象需要复制,从刚诞生的分配区之后需要进行复制整理,因此需要划分个区域可以便于快速复制清除,两者交换可以立即得到一块新的空间

    但是会浪费内存

    因为新生代无法对对象过多的情况进行有效处理,所以对于批量生成的对象或者当s1或者s2区域无法再分配内存时直接将这些对象存放老年代

    我们需要看MarkSweepPolicy对于initialize_generations的实现:

    void MarkSweepPolicy::initialize_generations() {
      _generations = NEW_C_HEAP_ARRAY3(GenerationSpecPtr, 2, mtGC, 0, AllocFailStrategy::RETURN_NULL);
      if (_generations == NULL) {
        vm_exit_during_initialization("Unable to allocate gen spec");
      }
    
      _generations[0] = new GenerationSpec(Generation::DefNew, _initial_gen0_size, _max_gen0_size);
      _generations[1] = new GenerationSpec(Generation::MarkSweepCompact, _initial_gen1_size, _max_gen1_size);
    
      if (_generations[0] == NULL || _generations[1] == NULL) {
        vm_exit_during_initialization("Unable to allocate gen spec");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    至此,GC的初始化基本完毕

    下面需要初始化堆内存

    jint GenCollectedHeap::initialize() {
      CollectedHeap::pre_initialize();
    
      int i;
      _n_gens = gen_policy()->number_of_generations();
    
      size_t gen_alignment = Generation::GenGrain;
    
      _gen_specs = gen_policy()->generations();
    
      for (i = 0; i < _n_gens; i++) {
        _gen_specs[i]->align(gen_alignment);
      }
    
      char* heap_address;
      size_t total_reserved = 0;
      int n_covered_regions = 0;
      ReservedSpace heap_rs;
    
      size_t heap_alignment = collector_policy()->heap_alignment();
    
      heap_address = allocate(heap_alignment, &total_reserved,
                              &n_covered_regions, &heap_rs);
    
      _reserved = MemRegion((HeapWord*)heap_rs.base(),
                            (HeapWord*)(heap_rs.base() + heap_rs.size()));
    
      _reserved.set_word_size(0);
      _reserved.set_start((HeapWord*)heap_rs.base());
      size_t actual_heap_size = heap_rs.size();
      _reserved.set_end((HeapWord*)(heap_rs.base() + actual_heap_size));
    
      _rem_set = collector_policy()->create_rem_set(_reserved, n_covered_regions);
      set_barrier_set(rem_set()->bs());
    
      _gch = this;
    
      for (i = 0; i < _n_gens; i++) {
        ReservedSpace this_rs = heap_rs.first_part(_gen_specs[i]->max_size(), false, false);
        _gens[i] = _gen_specs[i]->init(this_rs, i, rem_set());
        heap_rs = heap_rs.last_part(_gen_specs[i]->max_size());
      }
      clear_incremental_collection_failed();
    
    
      return JNI_OK;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    在universe_init方法中,

    Metaspace::global_initialize();是对于Java元数据区的信息初始化

    而什么是元数据区呢?为什么要元数据区呢?

    在没有元数据区的时候,所有的Java对象,类信息,字节码等所有信息全部放在Java堆内存中,对于垃圾回收来讲,怎么去区分是对象呢还是一般类信息呢?

    这样导致耦合度太高,那么就需要将Java对象和描述对象,类信息等数据分开,便于垃圾回收专注于对Java对象的处理

    在这里插入图片描述

    因此,元空间保存了 类的信息

    而Java堆用于存放Java对象

    由于是进程虚拟内存元空间使用的VirtualSpace开辟内存,而且肯定是使用mmap进行空间映射,不用malloc,因为malloc是使用berk指针开辟空间,无法将底部的空间利用起来,出现内存泄漏的情况

    ClassLoaderData::init_null_class_loader_data(); 是对于classloader的初始化

    类加载器的数据,在这里插入图片描述
    类加载器的对象信息

    oop就是一个Java对象,因此ClassLoaderData其实就是与Java的ClassLoader绑定在一起的

    而init_null_class_loader_data就是初始化bootstrap 类加载器

    解释器初始化:

    在这里插入图片描述

    那么我们看看Interpreter的hpp

    在这里插入图片描述

    可以看到解释器的最终形态为:

    CppInterpreter和TemplateInterpreter

    而CC_INTERP_ONLY和NOT_CC_INTERP为宏定义,用编译期的时候根据编译配置选择最终执行的解释器

    由于我们研究c++的,也即零汇编的,那么可以将该class展开

    class Interpreter: public CC_INTERP_ONLY(CppInterpreter) NOT_CC_INTERP(TemplateInterpreter) {
    
      public:
      // Debugging/printing
      static InterpreterCodelet* codelet_containing(address pc)     { return (InterpreterCodelet*)_code->stub_containing(pc); }
    #ifndef CPU_ZERO_VM_INTERPRETER_ZERO_HPP
    #define CPU_ZERO_VM_INTERPRETER_ZERO_HPP
      public:
      static void invoke_method(Method* method, address entry_point, TRAPS) {
        ((ZeroEntry *) entry_point)->invoke(method, THREAD);
      }
      static void invoke_osr(Method* method,
                             address   entry_point,
                             address   osr_buf,
                             TRAPS) {
        ((ZeroEntry *) entry_point)->invoke_osr(method, osr_buf, THREAD);
      }
    
     public:
      static int expr_index_at(int i) {
        return stackElementWords * i;
      }
    
      static int expr_offset_in_bytes(int i) {
        return stackElementSize * i;
      }
    
      static int local_index_at(int i) {
        assert(i <= 0, "local direction already negated");
        return stackElementWords * i;
      }
    
    #endif // CPU_ZERO_VM_INTERPRETER_ZERO_HPP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    因为我们研究C++的代码,看继承关系需要看Cpp

    那么也就会调用CppInterpreter的initialized方法

    在这里插入图片描述

    在这里插入图片描述

    _code 是 StubQueue

    在这里插入图片描述

    StubQueue是一堆函数组成的队列 也就是解释器代码

    由于hotspot是一个热点编译

    当某一个方法执行到某一个次数的时候调用计数器(InvocationCounter)增加数值,到达阈值之后编译,当长时间不用的时候该值下降,下降到某个阈值的时候再解除编译

    在这里插入图片描述

    CppInterpreter的初始化:

    在这里插入图片描述

    TemplateInterpreter:

    在这里插入图片描述

    对于StubQueue

    在这里插入图片描述

    运行在解释器中的一小段代码

    在这里插入图片描述

    回到对CppInterpreter的研究

    在这里插入图片描述

    看到InterpreterGenerator需要分配对象,那么肯定会调用其构造方法

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    说明对于Cpp解释器来讲,没有任何汇编的东西

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    直接用c++的函数地址,强转为address

    而之前在generate_all中也见到过这种写法

    而address是一个在这里插入图片描述
    看到是u_char* 类型,但不重要,之后在使用的时候是可以直接强转为某一个特定函数的函数原型指针:返回值(*函数名)(入参) 匿名指针:返回值(*)(入参)

    如何执行?(返回值(*)(入参))()再需要进行address强转为该函数-> ``((返回值(*)(入参))address)()`

    回到CppInterpreterGenerator::generate_all()中:

    memset将对应的空间全部清零

    在这里插入图片描述

    由于Cpp为零汇编,所以我们在调用JNI时,必定直接使用了C++的代码:dlopen、dlsym等函数,直接调用,对于返回值,直接遵循C++函数调用规范,拿到返回值直接转化

    解释器需要执行方法:1.JNI相关方法 2.Java和字节码的相关方法 3.同步代码 4.同步代码与1 2组合的

    这些方法都需要入口: 入口在哪?函数指针嘛

    那么就是找到对应的函数指针入口点赋值即可

    在这里插入图片描述

    根据方法的类型,生成对应解释器的方法的入口表

    于是以后在调方法时只需要 kind方法类型 和 函数的指针(方法入口)

    在这里插入图片描述

    address AbstractInterpreterGenerator::generate_method_entry(
        AbstractInterpreter::MethodKind kind) {
      address entry_point = NULL;
    
      switch (kind) {
      case Interpreter::zerolocals:
      case Interpreter::zerolocals_synchronized:
        break;
    
      case Interpreter::native:
        entry_point = ((InterpreterGenerator*) this)->generate_native_entry(false);
        break;
    
      case Interpreter::native_synchronized:
        entry_point = ((InterpreterGenerator*) this)->generate_native_entry(false);
        break;
    
      case Interpreter::empty:
        entry_point = ((InterpreterGenerator*) this)->generate_empty_entry();
        break;
    
      case Interpreter::accessor:
        entry_point = ((InterpreterGenerator*) this)->generate_accessor_entry();
        break;
    
      case Interpreter::abstract:
        entry_point = ((InterpreterGenerator*) this)->generate_abstract_entry();
        break;
    
      case Interpreter::java_lang_math_sin:
      case Interpreter::java_lang_math_cos:
      case Interpreter::java_lang_math_tan:
      case Interpreter::java_lang_math_abs:
      case Interpreter::java_lang_math_log:
      case Interpreter::java_lang_math_log10:
      case Interpreter::java_lang_math_sqrt:
      case Interpreter::java_lang_math_pow:
      case Interpreter::java_lang_math_exp:
        entry_point = ((InterpreterGenerator*) this)->generate_math_entry(kind);
        break;
    
      case Interpreter::java_lang_ref_reference_get:
        entry_point = ((InterpreterGenerator*)this)->generate_Reference_get_entry();
        break;
    
      default:
        ShouldNotReachHere();
      }
    
      if (entry_point == NULL)
        entry_point = ((InterpreterGenerator*) this)->generate_normal_entry(false);
    
      return entry_point;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    generate_native_entry()

    generate_normal_entry()

    在这里插入图片描述

    最终调了CppInterpreter的native_entry和normal_entry

    而normal_entry方法最终开始循环执行字节码:

    在这里插入图片描述

    interpreter的生成器调用method_entry的宏定义,调用generate_method_entry方法,根据kind的类型调用C++函数的定义,把定义的函数指针放到_entry_table数组中。当需要调用的时候直接invoke,拿到当前方法的类型,在数组中匹配,找到对应类型的方法,将方法包装成Method之后执行normal_entry执行,然后执行main_loop主循环执行所有字节码

    注意,在解释器初始化中的stub与stubroutines的不一样

    stubroutines的是一堆外部需要调用的函数的指针

    而解释器中的是函数指针都是给解释器执行jni和Java代码的

    然后最终回到JavaMain中:

    在这里插入图片描述

    这是调用jni代码

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    所有的hotspot启动整理流程总结:

    1. 初始化os相关
    2. 初始化universe (GC和堆内存)
    3. 初始化Stubroutines(一组函数指针,核心为 call_stub从C调用java,原理为:entry_point调用)
    4. 初始化解释器 也是一组函数指针,将其设置为Cpp解释器或模板解释器地址,根据方法类型生成不同的入口,放入函数地址
    5. 初始化java.lang的常用类
    6. 获取到待执行的Java类的main函数的id,然后通过JNIEnv的桥梁与JVM沟通,于是调用invoke函数
    7. 调用JavaCalls类 获取到类初始化时,对 方法进行连接时,根据方法类型设置 第四步设置的方法入口函数,然后调用第三步生成的call_stub调用该函数入口






    在此感谢 《深入理解Java高并发编程》和《Tomcat源码全解和架构思维》的作者 黄俊 提供的学习思维和学习方式

  • 相关阅读:
    FRP之入门篇
    Fisher信息量与Fisher观测信息量
    第三十八章 构建数据库应用程序 - 处理表单提交请求
    贪吃蛇和俄罗斯方块
    CycloneDDS配置详细说明中文版(三)
    掌握结构化日志记录:全面指南
    音视频 SDL简介
    npm常用命令详解
    Vue3:将表格数据下载为excel文件
    【47C++STL-常用算法----5、常用算术生成算法
  • 原文地址:https://blog.csdn.net/qq_41755616/article/details/127427041