类的加载过程

Posted by hang.li on November 20, 2022

img.png

  1. 类的加载阶段 此时jvm会选用类加载器(ClassLoader)进行加载我们的类,通常将class文件以二进制流的方式读入到内存当中,生成Class对象
  2. 类的连接阶段 此阶段又分为3个小阶段 1)验证:启动项目时,遇到的java版本不匹配的的问题都是在此过程出现的;还会检验二进制流是否已魔数咖啡宝贝开始(0xCAFEBABE);还有常量池中的常量类型等其他信息。 2)准备:主要作用是为静态变量分配内存和分配初始值阶段(这里的初始值是默认的“0”值,并不是=右边的具体数值)

img.png

1
	public static int value = 123;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在准备阶段会给value静态变量赋值为0。

注意:如果是用final修饰的已知静态变量,在使用时,在javac编译成class时,就已经优化成具体值了 ```java class HelloWorld{

public final static int A_VALUE = 1;
public final static HelloWorld B_VALUE = new HelloWorld();
public final static String C_VALUE = "abc"; 
public final static String D_VALUE = new String("abc"); 

public static void main(String[] args){
	System.out.println(A_VALUE);
	System.out.println(B_VALUE);
	System.out.println(C_VALUE);
	System.out.println(D_VALUE);
} } ``` jd-gui反编译后结果: ![img.png](/img/in-post/java/java-decomplier.png) 3) 解析:将字面量转换成具体引用的过程;引用经典程序HelloWorld中的main方法,使用javap -v反编译命令可以看到,
1
2
3
4
5
public class Demo {
        public static void main(String[] args) {
            System.out.println("Hello world!");
    }
}
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
// main方法反编译结果
   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
// 常量池
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // Hello world!
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // com/lihang/Demo
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/lihang/Demo;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               Demo.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello world!
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               com/lihang/Demo
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V

这里仅仅有一句打印”Hello world!“字符串语句 首先会使用getstatic指令获取静态变量(#2),#2又指向#21,#22两个位置,知道对应的位置可知,词句语义为,获取System类中的out常量

此阶段还会对方法,字段它们的可访问性(public、protected、 private、)进行检查

  1. 初始化阶段 对静态类变量的显示赋值,静态代码块中的显示赋值,按照代码顺序执行 编译后会生成一个clinit方法,借用刚才的helloworld程序,同样适用javap -v命令查看
1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {

    static {
        A_VALUE = "1";
    }

    public static String A_VALUE = "abc";

    public static void main(String[] args) {
        System.out.println(A_VALUE);
    }
}
1
因为之前连接阶段的准备过程中已经对A_VALUE进行了内存分配,和空值赋值
1
2
3
4
5
         0: ldc           #5                  // String 1
         2: putstatic     #3                  // Field A_VALUE:Ljava/lang/String;
         5: ldc           #6                  // String abc
         7: putstatic     #3                  // Field A_VALUE:Ljava/lang/String;
        10: return

这里的clinit方法,按顺序首先将“1”字符串赋值给A_VALUE变量,然后将“abc”字符串赋值给A_VALUE所以main函数中打印的值为“abc”。 注:clinit方法为线程安全(例如一些常见的懒汉式单例,使用此机制进行控制单实例)