类加载运行过程

一、类的加载,连接和初始化

1.0 问题

public class Singleton {
​
    private static Singleton singleton = new Singleton();// 第一种情况
    public static int counter1;
    public static int counter2 = 0;
//    private static Singleton singleton = new Singleton();  //第二种情况
​
    private Singleton(){
        counter1++;
        counter2++;
    }
​
    public static Singleton getInstance(){
        /**
         * 调用构造方法
         * ++
         */
        return singleton;
    }
​
    public static void main(String[] args) {
        /**
         * 调用了Singleton的静态方法
         * 加载该类   默认值
         * 初始化     赋初始值
         */
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1=="+singleton.counter1);
        System.out.println("counter2=="+singleton.counter2);
    }
}

第一种情况:counter1==1 counter2==0

第二种情况: counter1==1 counter2==1

(具体差异的原因,下面会解释)

1.1加载

类的加载是指查找并加载类的二进制数据。把类的.class文件中的数据读入到内存中,将其放入到方法区内,使用到类时才会加载,例如调用类的main()方法,new对象等等,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。

加载完成后,Class对象还不完整,所以此时的类还不可用。

例如上述java文件生成的class文件如下:

cafe babe 0000 0037 0046 0a00 0c00 2109
000a 0022 0900 0a00 2309 000a 0024 0a00
0a00 2509 0026 0027 1200 0000 2b0a 002c
002d 1200 0100 2b07 002f 0a00 0a00 2107
0030 0100 0863 6f75 6e74 6572 3101 0001
4901 0008 636f 756e 7465 7232 0100 0973
696e 676c 6574 6f6e 0100 0b4c 5369 6e67
6c65 746f 6e3b 0100 063c 696e 6974 3e01
0003 2829 5601 0004 436f 6465 0100 0f4c
696e 654e 756d 6265 7254 6162 6c65 0100
124c 6f63 616c 5661 7269 6162 6c65 5461
626c 6501 0004 7468 6973 0100 0b67 6574
496e 7374 616e 6365 0100 0d28 294c 5369
6e67 6c65 746f 6e3b 0100 046d 6169 6e01
0016 285b 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 2956 0100 0461 7267 7301
0013 5b4c 6a61 7661 2f6c 616e 672f 5374
7269 6e67 3b01 0008 3c63 6c69 6e69 743e
0100 0a53 6f75 7263 6546 696c 6501 000e
5369 6e67 6c65 746f 6e2e 6a61 7661 0c00
1200 130c 000d 000e 0c00 0f00 0e0c 0010
0011 0c00 1800 1907 0031 0c00 3200 3301
0010 426f 6f74 7374 7261 704d 6574 686f
6473 0f06 0034 0800 350c 0036 0037 0700
380c 0039 003a 0800 3b01 0009 5369 6e67
6c65 746f 6e01 0010 6a61 7661 2f6c 616e
672f 4f62 6a65 6374 0100 106a 6176 612f
6c61 6e67 2f53 7973 7465 6d01 0003 6f75
7401 0015 4c6a 6176 612f 696f 2f50 7269
6e74 5374 7265 616d 3b0a 003c 003d 0100
0b63 6f75 6e74 6572 313d 3d01 0100 176d
616b 6543 6f6e 6361 7457 6974 6843 6f6e
7374 616e 7473 0100 1528 4929 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 0100
136a 6176 612f 696f 2f50 7269 6e74 5374
7265 616d 0100 0770 7269 6e74 6c6e 0100
1528 4c6a 6176 612f 6c61 6e67 2f53 7472
696e 673b 2956 0100 0b63 6f75 6e74 6572
323d 3d01 0700 3e0c 0036 0042 0100 246a
6176 612f 6c61 6e67 2f69 6e76 6f6b 652f
5374 7269 6e67 436f 6e63 6174 4661 6374
6f72 7907 0044 0100 064c 6f6f 6b75 7001
000c 496e 6e65 7243 6c61 7373 6573 0100
9828 4c6a 6176 612f 6c61 6e67 2f69 6e76
6f6b 652f 4d65 7468 6f64 4861 6e64 6c65
7324 4c6f 6f6b 7570 3b4c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b4c 6a61 7661
2f6c 616e 672f 696e 766f 6b65 2f4d 6574
686f 6454 7970 653b 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 5b4c 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 3b29 4c6a
6176 612f 6c61 6e67 2f69 6e76 6f6b 652f
4361 6c6c 5369 7465 3b07 0045 0100 256a
6176 612f 6c61 6e67 2f69 6e76 6f6b 652f
4d65 7468 6f64 4861 6e64 6c65 7324 4c6f
6f6b 7570 0100 1e6a 6176 612f 6c61 6e67
2f69 6e76 6f6b 652f 4d65 7468 6f64 4861
6e64 6c65 7300 2100 0a00 0c00 0000 0300
0900 0d00 0e00 0000 0900 0f00 0e00 0000
0a00 1000 1100 0000 0400 0200 1200 1300
0100 1400 0000 4b00 0200 0100 0000 152a
b700 01b2 0002 0460 b300 02b2 0003 0460
b300 03b1 0000 0002 0015 0000 0012 0004
0000 000d 0004 000e 000c 000f 0014 0010
0016 0000 000c 0001 0000 0015 0017 0011
0000 0009 0018 0019 0001 0014 0000 001c
0001 0000 0000 0004 b200 04b0 0000 0001
0015 0000 0006 0001 0000 0017 0009 001a
001b 0001 0014 0000 0065 0002 0002 0000
0025 b800 054c b200 062b 57b2 0002 ba00
0700 00b6 0008 b200 062b 57b2 0003 ba00
0900 00b6 0008 b100 0000 0200 1500 0000
1200 0400 0000 2000 0400 2100 1400 2200
2400 2300 1600 0000 1600 0200 0000 2500
1c00 1d00 0000 0400 2100 1000 1100 0100
0800 1e00 1300 0100 1400 0000 2b00 0200
0000 0000 0f03 b300 03bb 000a 59b7 000b
b300 04b1 0000 0001 0015 0000 000a 0002
0000 000a 0004 000b 0003 001f 0000 0002
0020 0041 0000 000a 0001 003f 0043 0040
0019 0028 0000 000e 0002 0029 0001 002a
0029 0001 002e 

使用javap -v命令反编译后得到的,可读性更好,两者本质一样,是对应的。

geniusdew@localhost jvm % javap -v Singleton.class 
Classfile /Users/geniusdew/ZLocalRepository/study_code/jvm/out/production/jvm/Singleton.class
  Last modified 2021-5-6; size 1302 bytes
  MD5 checksum 7f99e8a687d2a8f7954e1f1a7e806fe2
  Compiled from "Singleton.java"
public class Singleton
  minor version: 0
  major version: 55
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#33        // java/lang/Object."<init>":()V
   #2 = Fieldref           #10.#34        // Singleton.counter1:I
   #3 = Fieldref           #10.#35        // Singleton.counter2:I
   #4 = Fieldref           #10.#36        // Singleton.singleton:LSingleton;
   #5 = Methodref          #10.#37        // Singleton.getInstance:()LSingleton;
   #6 = Fieldref           #38.#39        // java/lang/System.out:Ljava/io/PrintStream;
   #7 = InvokeDynamic      #0:#43         // #0:makeConcatWithConstants:(I)Ljava/lang/String;
   #8 = Methodref          #44.#45        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #9 = InvokeDynamic      #1:#43         // #1:makeConcatWithConstants:(I)Ljava/lang/String;
  #10 = Class              #47            // Singleton
  #11 = Methodref          #10.#33        // Singleton."<init>":()V
  #12 = Class              #48            // java/lang/Object
  #13 = Utf8               counter1
  #14 = Utf8               I
  #15 = Utf8               counter2
  #16 = Utf8               singleton
  #17 = Utf8               LSingleton;
  #18 = Utf8               <init>
  #19 = Utf8               ()V
  #20 = Utf8               Code
  #21 = Utf8               LineNumberTable
  #22 = Utf8               LocalVariableTable
  #23 = Utf8               this
  #24 = Utf8               getInstance
  #25 = Utf8               ()LSingleton;
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               args
  #29 = Utf8               [Ljava/lang/String;
  #30 = Utf8               <clinit>
  #31 = Utf8               SourceFile
  #32 = Utf8               Singleton.java
  #33 = NameAndType        #18:#19        // "<init>":()V
  #34 = NameAndType        #13:#14        // counter1:I
  #35 = NameAndType        #15:#14        // counter2:I
  #36 = NameAndType        #16:#17        // singleton:LSingleton;
  #37 = NameAndType        #24:#25        // getInstance:()LSingleton;
  #38 = Class              #49            // java/lang/System
  #39 = NameAndType        #50:#51        // out:Ljava/io/PrintStream;
  #40 = Utf8               BootstrapMethods
  #41 = MethodHandle       #6:#52         // invokestatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #42 = String             #53            // counter1==
  #43 = NameAndType        #54:#55        // makeConcatWithConstants:(I)Ljava/lang/String;
  #44 = Class              #56            // java/io/PrintStream
  #45 = NameAndType        #57:#58        // println:(Ljava/lang/String;)V
  #46 = String             #59            // counter2==
  #47 = Utf8               Singleton
  #48 = Utf8               java/lang/Object
  #49 = Utf8               java/lang/System
  #50 = Utf8               out
  #51 = Utf8               Ljava/io/PrintStream;
  #52 = Methodref          #60.#61        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #53 = Utf8               counter1==
  #54 = Utf8               makeConcatWithConstants
  #55 = Utf8               (I)Ljava/lang/String;
  #56 = Utf8               java/io/PrintStream
  #57 = Utf8               println
  #58 = Utf8               (Ljava/lang/String;)V
  #59 = Utf8               counter2==
  #60 = Class              #62            // java/lang/invoke/StringConcatFactory
  #61 = NameAndType        #54:#66        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #62 = Utf8               java/lang/invoke/StringConcatFactory
  #63 = Class              #68            // java/lang/invoke/MethodHandles$Lookup
  #64 = Utf8               Lookup
  #65 = Utf8               InnerClasses
  #66 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #67 = Class              #69            // java/lang/invoke/MethodHandles
  #68 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #69 = Utf8               java/lang/invoke/MethodHandles
{
  public static int counter1;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC
​
  public static int counter2;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC
​
  public static Singleton getInstance();
    descriptor: ()LSingleton;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #4                  // Field singleton:LSingleton;
         3: areturn
      LineNumberTable:
        line 23: 0
​
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokestatic  #5                  // Method getInstance:()LSingleton;
         3: astore_1
         4: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: aload_1
         8: pop
         9: getstatic     #2                  // Field counter1:I
        12: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
        17: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        23: aload_1
        24: pop
        25: getstatic     #3                  // Field counter2:I
        28: invokedynamic #9,  0              // InvokeDynamic #1:makeConcatWithConstants:(I)Ljava/lang/String;
        33: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        36: return
      LineNumberTable:
        line 32: 0
        line 33: 4
        line 34: 20
        line 35: 36
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      37     0  args   [Ljava/lang/String;
            4      33     1 singleton   LSingleton;
​
  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: iconst_0
         1: putstatic     #3                  // Field counter2:I
         4: new           #10                 // class Singleton
         7: dup
         8: invokespecial #11                 // Method "<init>":()V
        11: putstatic     #4                  // Field singleton:LSingleton;
        14: return
      LineNumberTable:
        line 10: 0
        line 11: 4
}
SourceFile: "Singleton.java"
InnerClasses:
     public static final #64= #63 of #67; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #41 invokestatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #42 counter1==
  1: #41 invokestatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #46 counter2==
​

1.1.1加载.class文件的方式

– 从本地系统中直接加载

– 通过网络下载.class文件

– 从zip,jar等归档文件中加载.class文件

– 从专有数据库中提取.class文件

– 将Java源文件动态编译为.class文件

类的加载的最终产品是位于堆区中的 Class对象 ,Class对象封装了类在方法区内的数据结构 ,并且向Java程序员提供了访问方法区内的数据结构的接口

1.1.2有两种类型的类加载器

Java虚拟机自带的加载器

• 根类加载器(Bootstrap)

• 扩展类加载器(Extension)

• 系统类加载器(System)

– 用户自定义的类加载器

• java.lang.ClassLoader的子类

• 用户可以定制类的加载方式

类加载器并不需要等到某个类被“首次主动使用”时再加载它

JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误 ,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)

如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

1.2连接

当类被加载后就进入连接阶段,连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。

这一阶段包括:

1.2.1验证

字节码验证器将验证生成的字节码是否正确。如果验证失败,将得到验证错误

类的验证的内容

– 类文件的结构检查:确保类文件遵从Java类文件的固定格式

– 语义检查:确保类本身符合Java语法规定,比如final类型的类没有子类,final类型的方法没有被重写

– 字节码验证:确保字节码流可以被Java虚拟机安全执行

– 二进制兼容性的验证:确保相互引用的类之间协调一致

例如如果我们将上面的class文件手动修改一部分,那么验证将不通过。

1.2.2准备

为静态变量分配内存并设置默认的初始值。例如为int类型变量分配4个字节的内存空间,并赋予默认值0;为long类型的变量分配8个字节的内存空间,并赋予默认值0等等

1.2.3解析

将符号引用替换为直接引用。例如在A类中包含了对B类add方法的引用,它由add方法的全名和相关描述符组成。在解析阶段,JVM会把该符号引用替换为指针,该指针指向B类add方法在方法区的内存位置,这个指针就是直接引用。

这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。

1.3初始化

所有静态变量都将初始化为指定的值,并且静态块也会被执行。

1.3.1静态变量初始化两种途径

1.在声明处初始化;

2.在静态代码块中初始化。

静态变量的声明语句以及静态代码块都可以看作是类的初始化语句,JVM会按照初始化语句在类中的顺序来依次执行。

1.3.2类初始化的步骤

1.假如类还没有被加载和连接,先进行加载和连接;

2.如果类存在直接父类并且这个类还没有被初始化,那么就先初始化父类;

但是该规则不适用于接口,在初始化一个类时,并不会先初始化它所实现的接口;在初始化接口时,也不会先初始化它的父接口。只有当首次使用特定接口的静态变量时,才会导致接口初始化。

public class Child1 extends Parent1{
​
    static int a = 3;
​
    static {
        System.out.println("Child1");
    }
}
​
class Parent1{
    static int b = 4;
​
    static{
        System.out.println("Parent1");
    }
}
​
class Test3{
    static{
        System.out.println("Test3");
    }
​
    public static void main(String[] args) {
        System.out.println(Child1.a);
    }
}

结果:

Test3 Parent1 Child1 3

3.如果类中存在初始化语句,就依次执行这些初始化语句。

1.3.3类初始化的时机

Java程序对类的使用分为:主动使用,被动使用。所有的JVM实现必须在每个类或接口被Java程序“首次主动使用”时才初始化,其他情况使用类的方式都看作是被动使用,都不会导致类的初始化

主动使用包括:

1.创建类实例

2.访问某个类或接口的静态变量,或者对静态变量赋值

只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用

public class Child3 extends Parent3{
​
    static {
        System.out.println("Child3");
    }
}
​
class Parent3{
​
    static int a = 3;
​
    static{
        System.out.println("Parent3");
    }
​
    static void dosomething(){
        System.out.println("do something");
    }
}
​
class Test5{
​
    public static void main(String[] args) {
        //此时静态变量a和静态方法dosomething并不在Child中定义,所以不认为是主动使用
        System.out.println(Child3.a);
​
        Child3.dosomething();
    }
}

结果:

Parent3 3 do something

3.调用类的静态方法

4.反射

5.初始化一个类的子类

6.JVM启动时被标明为启动类的类

调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

img

使用指定的二进制名称来加载类。此方法使用与loadClass(String, boolean)方法相同的方式搜索类。Java 虚拟机调用它来分析类引用。调用此方法等效于调用loadClass(name, false)。

public class LoadClass {
​
    static {
        System.out.println("LoadClass");
    }
}
​
class Test6{
​
    public static void main(String[] args) throws ClassNotFoundException {
        //获得系统类加载器
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
​
        //不是对类的主动使用,不会加载静态代码块
        Class aClass = classLoader.loadClass("LoadClass");
​
        System.out.println("---------------------------------------");
​
        //对类的主动使用,加载静态代码块
        Class<?> systemLoader = Class.forName("LoadClass");
    }
​
​
}

结果:

--------------------------------------- LoadClass

public class Final {
​
    //编译时常量,编译时确定,类不会被初始化
    public static final int x = 6/3;
​
    static {
        System.out.println("Final static block");
    }
​
}
​
class Test{
    public static void main(String[] args) {
        System.out.println(Final.x);
    }
}
public class Final2 {
​
    //运行时确定x值,需要初始化类
    public static final int x = new Random().nextInt(100);
​
    static {
        System.out.println("Final2 static block");
    }
​
}
​
class Test2{
    public static void main(String[] args) {
        System.out.println(Final2.x);
    }
}

1结果:2

2结果:Final2 static block 94(随机)

二、类加载器

类加载器用来把类加载到JVM中。从jdk1.2开始,类加载过程采用父亲委托机制,在此委托机制中,除了根加载器以外,其余的类加载器都有且只有一个父加载器

2.1 类加载器

1.根类加载器:该加载器没有父加载器。负责加载位于JRE的lib目录下的核心类库,如java.lang.*等。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。

根类加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,没有继承java.lang.ClassLoader类

2.扩展类加载器:父加载器为根类加载器。从java.ext.dirs系统属性所指定的目录中加载类库,或者位于JRE的lib目录下的ext扩展目录中的JAR类包,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展加载器加载。

扩展类加载器是纯Java类,是java.lang.ClassLoader的子类

3.系统类加载器(应用类加载器):他的父加载器是扩展类加载器。从java.class.path系统属性所指定的目录中加载类,或者环境变量classpath下。他是用户自定义的类加载器的默认父加载器。

系统类加载器是纯Java类,是java.lang.ClassLoader的子类

4.自定义类加载器:除了以上虚拟机自带的加载器以外,用户还可以自定义类加载器,负责加载用户自定义路径下的类包

所有用户自定义的类加载器应该继承java.lang.ClassLoader类

若有一个类加载器能成功加载某个类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象的引用的类加载器(包括定义类加载器)都被称为初始类加载器

2.2 类加载器初始化

1.根加载器(C++实现)会创建JVM启动器实例sun.misc.Launcher.getLauncher()

2.Launcher构造方法里分别创建了sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)

3.默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序

4.调用classLoader的loadClass("className")方法加载要运行的类,采用双亲委派机制

Launcher源码:

	private static Launcher launcher = new Launcher();
  private static String bootClassPath = System.getProperty("sun.boot.class.path");
  private ClassLoader loader;
	......

	public static Launcher getLauncher() {
        return launcher;
  }
	
	public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
          	//构造扩展类加载器,在构造的过程中将其父加载器设置为null
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
          	//构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader
          	//Launcher的loader属性值是AppClassLoader,默认都是用这个类加载器来加载自己的应用程序
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

   }

	 //获取 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
	 public ClassLoader getClassLoader() {
       return this.loader;
   }

2.3 父亲委托机制/双亲委派机制

加载器父子层级结构:加载器之间的父子关系实际上指的是加载器对象之间的包装关系,而不是类之间的继承关系。一对父子加载器可能是同一个加载器类的两个实例,也可能不是。在子类加载器中包装了一个父加载器对象。

双亲委派机制:加载某个类时会先一层层委托父加载器寻找目标类,如果父加载器在自己的加载类路径下找不到目标类,则在自己的类加载路径中查找并载入目标类。

ClassLoader源码:

此方法的默认实现将按以下顺序搜索类:

  1. 调用findLoadedClass(String)来检查是否已经加载类。

  2. 双亲委派机制:在父类加载器上调用loadClass方法。如果父类加载器为 null,则使用虚拟机的内置类加载器。

  3. 调用findClass(String)方法查找类。

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
          	// 检查当前类加载器是否已经加载过该类,如加载过直接返回
            Class<?> c = findLoadedClass(name);//C++
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                  	//如果当前加载器父加载器不为空,则委托父加载器加载该类
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    //如果当前加载器父加载器为空,则委托根加载器加载该类  
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                  	//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {//是否执行
                resolveClass(c);
            }
            return c;
        }
    }

优点:提高系统的安全性。在此机制下,用户自定义的类加载器不可能加载应该由父加载器加载的核心类,从而可以防止核心API库被随意篡改。例如,java.lang.Object类总是由根类加载器加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类,同时也会避免重复加载类。

2.4 自定义类加载器

自定义类加载器,只需要:

1.继承java.lang.ClassLoader类

2.覆盖它的findClass(String name)方法,该方法根据参数指定的类的名字,返回对应的Class对象的引用。

代码:

import java.io.*;
​
public class MyClassLoader extends ClassLoader{
​
    /**
     * 类加载器名字
     */
    private String name;
​
    /**
     * 类加载的路径
     */
    private String path = "d://";
​
    /**
     * class文件扩展名
     */
    private final String fileType = ".class";
​
    public MyClassLoader(String name){
        //让系统类加载器成为该类加载器的父加载器
        super();
​
        this.name = name;
    }
​
    public MyClassLoader(ClassLoader parent,String name){
        //显式指定当前类加载器的父加载器
        super(parent);
​
        this.name = name;
    }
​
    @Override
    public String toString() {
        return this.name;
    }
​
​
    public String getPath() {
        return path;
    }
​
    public void setPath(String path) {
        this.path = path;
    }
​
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = this.loadClassData(name);
​
        return this.defineClass(name,data,0,data.length);
    }
​
    /**
     * 根据类的名字来得到类(class文件)的二进制的字节数组
     * @param name
     * @return
     */
    private byte[] loadClassData(String name){
​
        /**
         * 从硬盘中通过输入流把二进制数据加载到内存中,输出到一个字节数组输出流里面,
         * 然后通过toByteArray方法,转换成字节数组,赋值给data,返回。
         */
        InputStream inputStream = null;
        byte[] data = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
​
        try{
            /**
             * 包名转换成文件路径
             * .转换成\
             */
            this.name = this.name.replace(".","\\");
​
            inputStream = new FileInputStream(new File(path + name + fileType));
​
            byteArrayOutputStream = new ByteArrayOutputStream();
​
            int ch = 0;
            while (-1 != (inputStream.read())){
                //class文件的信息已经读到byteArrayOutputStream中
                byteArrayOutputStream.write(ch);
            }
​
            //转换成字节数组
            data = byteArrayOutputStream.toByteArray();
​
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            //关闭流
            try {
                inputStream.close();
                byteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
​
        return data;
​
    }
​
    public static void main(String[] args) throws Exception {
​
        MyClassLoader myClassLoader1 = new MyClassLoader("loader1");
​
        myClassLoader1.setPath("d:\\mypath\\server\\");
​
        MyClassLoader myClassLoader2 = new MyClassLoader(myClassLoader1,"loader2");
​
        myClassLoader2.setPath("d:\\mypath\\client\\");
​
//        MyClassLoader myClassLoader3 = new MyClassLoader(null,"loader3");
//
//        myClassLoader3.setPath("d:\\mypath\\other\\");
//
//        test(myClassLoader2);
//        test(myClassLoader3);
​
​
        /**
         * 不同类加载器加载的类相互不可见
         */
//        Class clazz = myClassLoader1.loadClass("Sample");
//        Object object = clazz.newInstance();
//        Sample sample = (Sample) object;
//
//        System.out.println(sample.v);
​
​
        /**
         * 通过反射突破限制
         */
//        Class clazz = myClassLoader1.loadClass("Sample");
//        Object object = clazz.newInstance();
//        Field field = clazz.getField("v");
//        int v = field.getInt(object);
//        System.out.println("v = " + v);
​
        /**
         * 类的卸载
         */
        Class clazz = myClassLoader1.loadClass("Sample");
        System.out.println(clazz.hashCode());
        Object object = clazz.newInstance();
​
        myClassLoader1 = null;
        clazz = null;
        object = null;
​
        myClassLoader1 = new MyClassLoader("loader1");
        myClassLoader1.setPath("d:\\mypath\\server\\");
        clazz = myClassLoader1.loadClass("Sample");
        System.out.println(clazz.hashCode());
​
​
    }
​
    public static void test(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
​
        Class clazz = classLoader.loadClass("Sample");
​
        Object o = clazz.newInstance();
    }
}

现在就可以使用MyClassLoader来加载想要加载的类,首先会委托父加载器进行加载。

2.5 打破双亲委派机制

双亲委派机制在ClassLoader中的loadClass方法已经定义,所以要打破双亲委派机制,需要重写loadClass方法。

注:jdk核心的包,都不能用自己的加载器加载,会报java.lang.SecurityException: Prohibited package name: java.lang。所以在实现打破双亲委派机制时,loadClass方法内需要判断核心类库仍需要由根加载器来加载,自己的应用程序可由自定义类加载器加载。

2.6 命名空间和运行时包

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字相同的两个类;在不同的命名空间中,有可能会出现类的完整名字相同的两个类

由同一类加载器加载的属于相同包的类组成了运行时包。决定了两个类是不是属于同一个运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(即默认访问级别的类和类成员)。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。假设用户自己定义了一个类java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库java.lang.*由不同的类加载器加载,他们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。

2.7 不同类加载器的命名空间关系

同一个命名空间内的类是相互可见的。

子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器加载的类。例如系统加载器加载的类能看见根类加载器加载的类。

由父加载器加载的类不能看见子加载器加载的类。

如果两个加载器之间没有直接或间接的父子关系,那么他们各自加载的类相互不可见。

当两个不同的命名空间内的类相互不可见时,可采用Java反射机制来访问对方实例的属性和方法。

三、类的卸载

当一个类被加载,连接和初始化后,他的生命周期就开始了。当代表该类的Class对象不再被引用,Class对象就会结束生命周期,该类在方法区内的数据也会被卸载,从而结束该类的生命周期。一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。

由JVM自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。Java虚拟机始终会引用这些类加载器,而这些类加载器则会始终引用他们所加载的类的Class对象,因此这些Class对象始终时可触及的。

由用户自定义的类加载器所加载的类是可以被卸载的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值