1. 类加载的过程

169 阅读19分钟

1. 类加载器初始化的过程

现在有个类 com.jason.OutIndexTest 里有个main方法

public class OutIndexTest {

    public static int i = 9527;
    public static Student student = new Student();

    public Map getMap() {
        Map<String, String> map = new HashMap<String, String>(2);
        map.put("1", "1");
        map.put("2", "1");
        map.put("3", "1");
        map.put("4", "1");
        return map;
    }
    public static void main(String[] args) {
        OutIndexTest outIndexTest = new OutIndexTest();
        Map map = outIndexTest.getMap();
        System.out.println(map);
    }
}

通常我们执行main方法程序就可以运行了,整个过程是如何被加载运行的呢?为什么运行main就可以得到结果呢? 类加载流程宏观图:如下图: image.png 备注: 1.windows上的java启动程序是java.exe 2.c语言部分,java部分我们需要掌握的部分

第一步:  java调用底层的jvm.dll文件创建java虚拟机(这一步由C++实现) . 这里java.exe是c++写的代码, 调用的jvm.dll也是c++底层的一个函数. 通过调用jvm.dll文件(dll文件相当于java的jar包), 会创建java虚拟机. java虚拟机的启动都是c++程序实现的.

第二步: 在启动虚拟机的过程中, 会创建一个引导类加载器的实例. 这个引导类的加载器是C语言实现的. 然后jvm虚拟机就启动起来了.

第三步:  接下来,C++语言会调用java的启动程序.刚刚只是创建了java虚拟机, java虚拟机里面还有很多启动程序. 其中有一个程序叫做Launcher. 类全称是sun.misc.Launcher. 通过启动这个java类, 会由这个类引导加载器加载并创建很多其他的类加载器. 而这些加载器才是真正启动并加载磁盘上的字节码文件.

第四步:真正的去加载本地磁盘的字节码文件,然后启动执行main方法. (这一步后面会详细说,到底是怎么加载本地磁盘的字节码文件的。)

第五步: main方法执行完毕, 引导类加载器会发起一个c++调用, 销毁JVM

以上就是启动一个main方法, 这个类加载的全部过程

下面我们重点来看一下,我们的类com.jason.OutIndexTest 是怎么被加载到java虚拟机里面去的?

2. 类加载的过程

上面的com.jason.OutIndexTest类最终会生成class字节码文件,字节码文件是怎么被加载到JVM虚拟机里面的呢? 列加载有五步:加载,验证,准备,解析,初始化那么这五步都是干什么的呢?我们来看一下

我们的类在哪里呢? 在磁盘里(比如: target文件夹下的class文件), 我们先要将class类加载到内存中. 加载到内存区域以后, 不是简简单单的转换成二进制字节码文件,他会经过一系列的过程. 比如: 验证, 准备, 解析, 初始化等. 把这一些列的信息转变成类元信息, 放到内存里面去. 我们来看看具体的过程

image.png

第一步: 加载.

将class类加载到java虚拟机的内存里去, 在加载到内存之前, 会有一系列的操作。第一步是验证字节码。

image.png

第二步:验证

验证字节码加载是否正确, 比如:打开一个字节码文件。打眼一看, 感觉像是乱码, 实际上不是的. 其实,这里面每个字符串都有对应的含义. 那么文件里面的内容我们能不能替换呢?当然不能, 一旦替换, 就不能执行成功了. 所以, 第一步:验证, 验证什么呢?

验证字节码加载是否正确: 格式是否正确. 内容是否符合java虚拟机的规范.

第三步:准备

验证完了, 接下来是准备. 准备干什么呢? 比如我们的类Math, 他首先会给Math里的静态变量赋值一个初始值. 比如我们OutIndexTest里有两个静态变量

    public static Student student = new Student();

在准备的过程中, 就会给这两个变量赋初始值, 这个初始值并不是真实的值,  比如i的初始值是0. 如果是boolean类型, 就赋值为false.如果是对象比如Student ,就赋值为null. 也就是说, 准备阶段赋的值是jvm固定的, 不是我们定义的值. 如果一个final的常量, 比如public static final String name="zhangsan", 那么他在初始化的时候, 是直接赋初始值"zhangsan"的. 这里只是给静态变量赋初始值

第四步:解析

接下来说说解析的过程. 解析的过程略微复杂, 解析是将"符号引用"转变为直接引用.

什么是符号引用呢?

比如我们的程序中的main方法. 写法是固定的, 我们就可以将main当成一个符号. 比如上面的 i,int,static我们都可以将其称之为符号, java虚拟机内部有个专业名词,把他叫做符号. 这些符号被加载到内存里都会对应一个地址. 将"符号引用"转变为直接引用, 指的就是, 将main,i,int 等这些符号转变为对应的内存地址. 这个地址就是代码的直接引用. 根据直接引用的值,我们就可以知道代码在什么位置.然后拿到代码去真正的运行.

将符号引用转变为"内存地址", 这种有一个专业名词, 叫静态链接. 上面的解析过程就相当于静态链接的过程. 类加载期间,完成了符号到内存地址的转换. 有静态链接, 那么与之对应的还有动态链接.

什么是动态链接呢?

        OutIndexTest outIndexTest = new OutIndexTest();
        Map map = outIndexTest.getMap();
        System.out.println(map);
    }

比如上端代码,只有我们运行到Map map = outIndexTest.getMap(); 才会去加载getMap()这个方法。也就是说在加载的时候, 我不一定会把compute()这个方法解析成内存地址. 只有当运行到这行代买的时候, 才会解析. 我们来看看汇编代码

  Last modified Jan 8, 2022; size 1241 bytes
  MD5 checksum d498b2e3c18c3c05f7f801c957d20e46
  Compiled from "OutIndexTest.java"
public class com.jason.OutIndexTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #18.#44        // java/lang/Object."<init>":()V
   #2 = Class              #45            // java/util/HashMap
   #3 = Methodref          #2.#46         // java/util/HashMap."<init>":(I)V
   #4 = String             #47            // 1
   #5 = InterfaceMethodref #48.#49        // java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
   #6 = String             #50            // 2
   #7 = String             #51            // 3
   #8 = String             #52            // 4
   #9 = Class              #53            // com/jason/OutIndexTest
  #10 = Methodref          #9.#44         // com/jason/OutIndexTest."<init>":()V
  #11 = Methodref          #9.#54         // com/jason/OutIndexTest.getMap:()Ljava/util/Map;
  #12 = Fieldref           #55.#56        // java/lang/System.out:Ljava/io/PrintStream;
  #13 = Methodref          #57.#58        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #14 = Fieldref           #9.#59         // com/jason/OutIndexTest.i:I
  #15 = Class              #60            // com/jason/Student
  #16 = Methodref          #15.#44        // com/jason/Student."<init>":()V
  #17 = Fieldref           #9.#61         // com/jason/OutIndexTest.student:Lcom/jason/Student;
  #18 = Class              #62            // java/lang/Object
  #19 = Utf8               i
  #20 = Utf8               I
  #21 = Utf8               student
  #22 = Utf8               Lcom/jason/Student;
  #23 = Utf8               <init>
  #24 = Utf8               ()V
  #25 = Utf8               Code
  #26 = Utf8               LineNumberTable
  #27 = Utf8               LocalVariableTable
  #28 = Utf8               this
  #29 = Utf8               Lcom/jason/OutIndexTest;
  #30 = Utf8               getMap
  #31 = Utf8               ()Ljava/util/Map;
  #32 = Utf8               map
  #33 = Utf8               Ljava/util/Map;
  #34 = Utf8               LocalVariableTypeTable
  #35 = Utf8               Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;
  #36 = Utf8               main
  #37 = Utf8               ([Ljava/lang/String;)V
  #38 = Utf8               args
  #39 = Utf8               [Ljava/lang/String;
  #40 = Utf8               outIndexTest
  #41 = Utf8               <clinit>
  #42 = Utf8               SourceFile
  #43 = Utf8               OutIndexTest.java
  #44 = NameAndType        #23:#24        // "<init>":()V
  #45 = Utf8               java/util/HashMap
  #46 = NameAndType        #23:#63        // "<init>":(I)V
  #47 = Utf8               1
  #48 = Class              #64            // java/util/Map
  #49 = NameAndType        #65:#66        // put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  #50 = Utf8               2
  #51 = Utf8               3
  #52 = Utf8               4
  #53 = Utf8               com/jason/OutIndexTest
  #54 = NameAndType        #30:#31        // getMap:()Ljava/util/Map;
  #55 = Class              #67            // java/lang/System
  #56 = NameAndType        #68:#69        // out:Ljava/io/PrintStream;
  #57 = Class              #70            // java/io/PrintStream
  #58 = NameAndType        #71:#72        // println:(Ljava/lang/Object;)V
  #59 = NameAndType        #19:#20        // i:I
  #60 = Utf8               com/jason/Student
  #61 = NameAndType        #21:#22        // student:Lcom/jason/Student;
  #62 = Utf8               java/lang/Object
  #63 = Utf8               (I)V
  #64 = Utf8               java/util/Map
  #65 = Utf8               put
  #66 = Utf8               (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  #67 = Utf8               java/lang/System
  #68 = Utf8               out
  #69 = Utf8               Ljava/io/PrintStream;
  #70 = Utf8               java/io/PrintStream
  #71 = Utf8               println
  #72 = Utf8               (Ljava/lang/Object;)V
{
  public static int i;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

  public static com.jason.Student student;
    descriptor: Lcom/jason/Student;
    flags: ACC_PUBLIC, ACC_STATIC

  public com.jason.OutIndexTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/jason/OutIndexTest;

  public java.util.Map getMap();
    descriptor: ()Ljava/util/Map;
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class java/util/HashMap
         3: dup
         4: iconst_2
         5: invokespecial #3                  // Method java/util/HashMap."<init>":(I)V
         8: astore_1
         9: aload_1
        10: ldc           #4                  // String 1
        12: ldc           #4                  // String 1
        14: invokeinterface #5,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
        19: pop
        20: aload_1
        21: ldc           #6                  // String 2
        23: ldc           #4                  // String 1
        25: invokeinterface #5,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
        30: pop
        31: aload_1
        32: ldc           #7                  // String 3
        34: ldc           #4                  // String 1
        36: invokeinterface #5,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
        41: pop
        42: aload_1
        43: ldc           #8                  // String 4
        45: ldc           #4                  // String 1
        47: invokeinterface #5,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
        52: pop
        53: aload_1
        54: areturn
      LineNumberTable:
        line 17: 0
        line 18: 9
        line 19: 20
        line 20: 31
        line 21: 42
        line 22: 53
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      55     0  this   Lcom/jason/OutIndexTest;
            9      46     1   map   Ljava/util/Map;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            9      46     1   map   Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #9                  // class com/jason/OutIndexTest
         3: dup
         4: invokespecial #10                 // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #11                 // Method getMap:()Ljava/util/Map;
        12: astore_2
        13: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        16: aload_2
        17: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        20: return
      LineNumberTable:
        line 25: 0
        line 26: 8
        line 27: 13
        line 28: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  args   [Ljava/lang/String;
            8      13     1 outIndexTest   Lcom/jason/OutIndexTest;
           13       8     2   map   Ljava/util/Map;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: sipush        9527
         3: putstatic     #14                 // Field i:I
         6: new           #15                 // class com/jason/Student
         9: dup
        10: invokespecial #16                 // Method com/jason/Student."<init>":()V
        13: putstatic     #17                 // Field student:Lcom/jason/Student;
        16: return
      LineNumberTable:
        line 13: 0
        line 14: 6
}
SourceFile: "OutIndexTest.java"

使用这个指令, 就可以查看OutIndexTest的二进制文件. 其实这个文件,就是上面那个二进制代码文件.

看看这里面有什么东西?

类的名称, 大小,修改时间, 大版本,小版本, 访问修饰符等等

  MD5 checksum d498b2e3c18c3c05f7f801c957d20e46
  Compiled from "OutIndexTest.java"
public class com.jason.OutIndexTest
  minor version: 0
  major version: 52

还有一个Constant pool 常量池. 这个常量池里面有很多东西. 我们重点看中间那一行. 第一列表示一个常量的标志符, 这个标识符可能在其他地方会用到. 第二列就表示常量内容.

   #1 = Methodref          #18.#44        // java/lang/Object."<init>":()V
   #2 = Class              #45            // java/util/HashMap
   #3 = Methodref          #2.#46         // java/util/HashMap."<init>":(I)V
   #4 = String             #47            // 1
   #5 = InterfaceMethodref #48.#49        // java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
   #6 = String             #50            // 2
   #7 = String             #51            // 3
   #8 = String             #52            // 4
   #9 = Class              #53            // com/jason/OutIndexTest
  #10 = Methodref          #9.#44         // com/jason/OutIndexTest."<init>":()V
  #11 = Methodref          #9.#54         // com/jason/OutIndexTest.getMap:()Ljava/util/Map;
  #12 = Fieldref           #55.#56        // java/lang/System.out:Ljava/io/PrintStream;
  #13 = Methodref          #57.#58        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #14 = Fieldref           #9.#59         // com/jason/OutIndexTest.i:I
  #15 = Class              #60            // com/jason/Student
  #16 = Methodref          #15.#44        // com/jason/Student."<init>":()V
  #17 = Fieldref           #9.#61         // com/jason/OutIndexTest.student:Lcom/jason/Student;
  #18 = Class              #62            // java/lang/Object
  #19 = Utf8               i
  #20 = Utf8               I
  #21 = Utf8               student
  #22 = Utf8               Lcom/jason/Student;
  #23 = Utf8               <init>
  #24 = Utf8               ()V
  #25 = Utf8               Code
  #26 = Utf8               LineNumberTable
  #27 = Utf8               LocalVariableTable
  #28 = Utf8               this
  #29 = Utf8               Lcom/jason/OutIndexTest;
  #30 = Utf8               getMap
  #31 = Utf8               ()Ljava/util/Map;
  #32 = Utf8               map
  #33 = Utf8               Ljava/util/Map;
  #34 = Utf8               LocalVariableTypeTable
  #35 = Utf8               Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;
  #36 = Utf8               main
  #37 = Utf8               ([Ljava/lang/String;)V
  #38 = Utf8               args
  #39 = Utf8               [Ljava/lang/String;
  #40 = Utf8               outIndexTest
  #41 = Utf8               <clinit>
  #42 = Utf8               SourceFile
  #43 = Utf8               OutIndexTest.java
  #44 = NameAndType        #23:#24        // "<init>":()V
  #45 = Utf8               java/util/HashMap
  #46 = NameAndType        #23:#63        // "<init>":(I)V
  #47 = Utf8               1
  #48 = Class              #64            // java/util/Map
  #49 = NameAndType        #65:#66        // put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  #50 = Utf8               2
  #51 = Utf8               3
  #52 = Utf8               4
  #53 = Utf8               com/jason/OutIndexTest
  #54 = NameAndType        #30:#31        // getMap:()Ljava/util/Map;
  #55 = Class              #67            // java/lang/System
  #56 = NameAndType        #68:#69        // out:Ljava/io/PrintStream;
  #57 = Class              #70            // java/io/PrintStream
  #58 = NameAndType        #71:#72        // println:(Ljava/lang/Object;)V
  #59 = NameAndType        #19:#20        // i:I
  #60 = Utf8               com/jason/Student
  #61 = NameAndType        #21:#22        // student:Lcom/jason/Student;
  #62 = Utf8               java/lang/Object
  #63 = Utf8               (I)V
  #64 = Utf8               java/util/Map
  #65 = Utf8               put
  #66 = Utf8               (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
  #67 = Utf8               java/lang/System
  #68 = Utf8               out
  #69 = Utf8               Ljava/io/PrintStream;
  #70 = Utf8               java/io/PrintStream
  #71 = Utf8               println
  #72 = Utf8               (Ljava/lang/Object;)V

这些标识符在后面都会被用到, 比如main方法

    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #9                  // class com/jason/OutIndexTest
         3: dup
         4: invokespecial #10                 // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #11                 // Method getMap:()Ljava/util/Map;
        12: astore_2
        13: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        16: aload_2
        17: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        20: return
      LineNumberTable:
        line 25: 0
        line 26: 8
        line 27: 13
        line 28: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  args   [Ljava/lang/String;
            8      13     1 outIndexTest   Lcom/jason/OutIndexTest;
           13       8     2   map   Ljava/util/Map;

这里用到了 #9 #10 #11 #12 #13 ,这些都是标识符的引用。 第一句 new了一个OutIndexTest ,我们汇编是怎么写的呢? 0: new #9 // class com/jason/OutIndexTest #9 是什么呢,我们去常量池看一下,#代表的OutIndexTest类 #9 = Class #53 // com/jason/OutIndexTest 这里要说的还是outIndexTest.getMap()这个方法, 不是在类加载的时候就被加载到内存中去了, 而是运行main方法的时候, 执行到这行代码才被加载进去, 这个过程叫做动态链接.

类加载的时候, 我们可以把"解析"理解为静态加载的过程. 一般像静态方法(例如main方法), 获取其他不变的静态方法会被直接加载到内存中, 因为考虑到性能, 他们加载完以后就不会变了, 就直接将其转变为在内存中的代码位置.

而像OutIndexTest.getMap()方法, 在加载过程中可能会变的方法(比如getMap是个多态,有多个实现), 那么在初始化加载的时候, 我们不会到他会调用谁, 只有到运行时才能知道代码的实现, 所以在运行的时候在动态的去查询他在内存中的位置, 这个过程就是动态加载

第五步: 初始化

对类的静态变量初始化为指定的值. 执行静态代码块. 比如代码

public static int i = 9527;
public static Student student = new Student();

在准备阶段将其赋值为0, 而在初始化阶段, 会将其赋值为设定的9527 Student初始化会加载Student.class 会进行加载,验证,准备,解析,初始化。

1.3 类的懒加载

类被加载到方法区中以后,主要包含:运行时常量池, 类型信息, 字段信息, 方法信息, 类加载器的引用, 对应class实例的引用等信息.

什么意思呢? 就是说, 当一个类被加载到内存, 这个类的常量,有常量名, 类型, 域信息等; 方法有方法名, 返回值类型, 参数类型, 方法作用域等符号信息都会被加载放入不同的区域.

注意: 如果主类在运行中用到其他类,会逐步加载这些类, 也就是说懒加载. 用到的时候才加载.

package com.jason;

public class TestDynamicLoad {
    static {
        System.out.println("TestDynamicLoad static module ");
    }

    public static void main(String[] args) {
        new A1();
        System.out.println("==========");
        A2 a2=null;
    }

}

class A1{
    static {
        System.out.println("class a1  static load");
    }
    public A1(){
        System.out.println("inital A1************");
    }
}
class A2{
    static {
        System.out.println("class a2  static load");
    }
    public A2(){
        System.out.println("inital A2************");
    }
}

这里定义了两个类A1 A2 ,当使用到哪一个的时候,哪一个才会被加载,比如:main方法中,B没有呗用到,所以,他不会呗加载到内存中。

class a1  static load
inital A1************
==========

我们可以看到A类被加载了,而B类没有被加载,原因B类只有声明,没有用到。 总结几点如下: 1.静态代码块在构造方法之前执行 2.没有被真正使用的类不会被加载

2.类加载器

类主要通过类加载器来加载, java里面有如下几种类加载器

1. 引导类加载器(Bootstrap ClassLoader)

在上面类加载流程中,说到在 [启动虚拟机的过程中, 会创建一个引导类加载器的实例] 这个引导类加载器的目的是什么呢?加载类

引导类加载器主要负责加载最最核心的java类型。 这些类库位于jre目录的lib目录下**. 比如:rt.jar, charset.jar等,

2. 扩展类加载器(Ext ClassLoader)

扩展类加载器主要是用来加载扩展的jar包。 加载jar的目录位于jre目录的lib/ext扩展目录中的jar包

3. 应用程序类加载器(App CloassLoader)

主要是用来加载用户自己写的类的。 负责加载classPath路径下的类包

4. 自定义类加载器

负责加载用户自定义路径下的类包

引导类加载器是由C++帮我们实现的, 然后c++语言会通过一个Launcher类将扩展类加载器(ExtClassLoader)和应用程序类加载器(AppClassLoader)构造出来, 并且把他们之间的关系构建好.

2.2 案列

案列一:测试jdk 自带的类加载器

package com.jason;

public class TestJDKClassLoader {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(sun.security.ec.SunEC.class.getClassLoader());
        System.out.println(TestJDKClassLoader.class.getClassLoader());
    }
}

我们来看这个简单的代码, 运行结果:

null
sun.misc.Launcher$ExtClassLoader@5e481248
sun.misc.Launcher$AppClassLoader@330bedb4

解析: 
第一个: String 是jdk自身自带的类, 所以, 他的类加载器是引导类加载器,引导类加载器是c++代码,所以这里返回null
第二个: 加密类的classloader, 这是jdk扩展包的一个类, jdk扩展包里面使用的是extClassLoader类加载器加载的 
第三个: 是我们当前自己定义的类, 会被AppClassLoader应用程序加载器加载.

我们看到ExtClassLoader和AppClassLoader都是Launcher类的一部分. 那Launcher类是什么东西呢?

上面有提到, Launcher类是jvm启动的时候由C++调用启动的一个类. 这个类引导加载器加载并创建其他的类加载器。

那么,第一个bootstrap引导类加载器, 那引导类加载器返回的为什么是null呢?

因为bootstrap引导类加载器, 他不是java的对象, 他是c++生成的对象, 所以这里是看不到的

案例二: BootstrapClassLoad和ExtClassLoader、AppClassLoader的关系

image.png 如上图,左边是C语言程序代码实现,右边是java 代码实现。这里是跨语言调用,JNI实现了有c++向java跨语言调用。c语言调用的第一个java类是Launcher类。 从这个图中我们可以看出,C++调用java创建JVM启动器, 其中一个启动器是Launcher, 他实际是调用了sun.misc.Launcher类的getLauncher()方法. 那我们就从这个方法入手看看到底是如何运行的?

我们看到Lanucher.java类是在核心的rt.jar包里的,Lanucher是非常核心的一个类。

image.png

我们看到getLauncher()类直接返回了launcher. 而launcher是一个静态对象变量, 这是一个单例模式 C++调用了getLauncher()-->直接返回了lanucher对象, 而launcher对象是在构建类的时候就已经初始化好了. 那么,初始化的时候做了哪些操作呢?接下来看看他的构造方法.

image.png 在构造方法里, 首先定义了一个ExtClassLoader. 这是一个扩展类加载器, 扩展类加载器调用的是getExtClassLoader(). 接下来看一看getExtClassLoader这个方法做了什么?

image.png

doPrivileged是一个权限校验的操作, 我们可以先不用管, 直接看最后一句, return new Launcher.ExtClassLoader(var0). 直接new了一个ExtClassLoader, 其中参数是var0, 代表的是ext扩展目录下的文件.

image.png 在ExtClassLoader(File[] var1)这个方法中, 这里第一步就是调用了父类的super构造方法. 而ExtClassLoader继承了谁呢? 我们可以看到他继承了URLClassLoader.

image.png 而URLClassLoader是干什么用的呢? 其实联想一下大概能够猜数来, 这里有一些文件路径, 通过文件路径加载class类.

我们继续看调用的super(parent), 我们继续往下走, 就会看到调用了ClassLoader接口的构造方法:

image.png 回到上一步我们在看一下,这里设置了ExtClassLoader的parent是谁? 注意看,我们发现, ExtClassLoader的parent类是null.

image.png 这就是传递过来的parent类加载器, 那么这里的parent类加载器为什么是null呢? 因为, ExtClassLoader的父类加载器是谁呢? 他是Bootstrap ClassLoader. 而BootStrap ClassLoader是C++的类加载器, 我们不能直接调用它, 所以, 设置为null. 其实, ExtClassLoader在初始化阶段就是调用了ExtClassLoader方法, 初始化了ExtClassLoader类

接下来,我们回到Launcher的构造方法, 看看Launcher接下来又做了什么?

image.png 可以看到, 接下来调了AppClassLoader的getAppClassLoader(var1), 这个方法. 需要注意一下的是var1这个参数. var1是谁呢? 向上看, 可以看到var1是ExtClassLoader.

image.png 这里第一句话就是获取当前项目的class 文件路径, 然后将其转换为URL. 并调用了Launcher.AppClassLoader(var1x, var0), 其中var1x是class类所在的路径集合, var0是扩展的类加载器ExtClassLoader, 接下来, 我们进入到这个方法里看一看

image.png AppClassLoader直接调用了其父类的构造方法, 参数是class类路径集合, 和ExtClassLoader

image.png

image.png 最后, 我们看到, 将ExtClassLoader传递给了parent变量. 这是定义在ClassLoader中的属性, 而ClassLoader类是所有类加载器的父类. 因此, 我们也可以看到AppClassLoader的父类加载器是ExtClassLoader

同时, 我们也看到了, C++在启动JVM的时候, 调用了Launcher启动类, 这个启动类同时加载了ExtClassLoader和AppClassLoader.

    public static void main(String[] args) {
//        OutIndexTest outIndexTest = new OutIndexTest();
//        Map map = outIndexTest.getMap();
//        System.out.println(map);
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassLoader = appClassLoader.getParent();
        ClassLoader bootstrapClassLoad = extClassLoader.getParent();


        System.out.println("bootstrap class loader: " + bootstrapClassLoad);
        System.out.println("ext class loader " + extClassLoader);
        System.out.println("app class loader " + appClassLoader);
    }
}

通过这个demo, 我们也可以看出, appClassLoader的父类是extClassLoader, extClassLoader的父类是bootstrapClassLoader

输出结果:

bootstrap class loader: null
ext class loader sun.misc.Launcher$ExtClassLoader@7ea987ac
app class loader sun.misc.Launcher$AppClassLoader@330bedb4

通过上面的源码分析,我们发现引导类加载器创建并加载了扩展类加载器和应用类加载器。而扩展类加载器的父加载器是引导类加载器。应用类加载器的父加载器是扩展类加载器。这个结构,决定了后面类的加载方式,也就是双亲委派机制。