- 类的加载阶段
此时jvm会选用类加载器(ClassLoader)进行加载我们的类,通常将class文件以二进制流的方式读入到内存当中,生成Class对象
- 类的连接阶段
此阶段又分为3个小阶段
1)验证:启动项目时,遇到的java版本不匹配的的问题都是在此过程出现的;还会检验二进制流是否已魔数咖啡宝贝开始(0xCAFEBABE);还有常量池中的常量类型等其他信息。
2)准备:主要作用是为静态变量分配内存和分配初始值阶段(这里的初始值是默认的“0”值,并不是=右边的具体数值)
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、)进行检查
- 初始化阶段
对静态类变量的显示赋值,静态代码块中的显示赋值,按照代码顺序执行
编译后会生成一个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方法为线程安全(例如一些常见的懒汉式单例,使用此机制进行控制单实例)