Skip to content

Commit f9ad756

Browse files
author
Tan Minghui
committed
Update articles
1 parent f41a31e commit f9ad756

File tree

6 files changed

+611
-0
lines changed

6 files changed

+611
-0
lines changed

Java/Java 类加载机制.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Java 类加载机制
2+
3+
![类加载过程-11.2kB](http://static.zybuluo.com/Rico123/ojhhtids41ivtuowfj74mkb2/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B)
4+
5+
## 阶段
6+
7+
### 加载
8+
9+
> 加载阶段是类加载过程的第一个阶段。在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中,接着会为这个类在 JVM 的方法区创建一个对应的 Class 对象,这个 Class 对象就是这个类各种数据的访问入口。
10+
11+
把代码数据加载到内存中。
12+
13+
这里有两个重点:
14+
15+
- **字节码来源**。一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
16+
- **类加载器**。一般包括**启动类加载器****扩展类加载器****应用类加载器**,以及用户的**自定义类加载器**
17+
18+
### 验证
19+
20+
主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
21+
22+
- 包括对于**文件格式的验证**,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?
23+
- 对于**元数据的验证**,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?
24+
- 对于**字节码的验证**,保证程序语义的合理性,比如要保证类型转换的合理性。
25+
- 对于**符号引用的验证**,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
26+
27+
### 准备(重点)
28+
29+
**准备阶段主要是为类的静态变量分配内存,并设置jvm默认的初始值。对于非静态的变量,则不会为它们分配内存。**
30+
31+
当完成字节码文件的校验之后,JVM 便会开始为类变量分配内存并初始化。这里需要注意两个关键点,即内存分配的对象以及初始化的类型。
32+
33+
- **内存分配的对象。**Java 中的变量有「类变量」和「类成员变量」两种类型,「类变量」指的是被 static 修饰的变量,而其他所有类型的变量都属于「类成员变量」。在准备阶段,JVM 只会为「类变量」分配内存,而不会为「类成员变量」分配内存。「类成员变量」的内存分配需要等到初始化阶段才开始。
34+
35+
例如下面的代码在准备阶段,只会为 factor 属性分配内存,而不会为 website 属性分配内存。
36+
37+
```
38+
public static int factor = 3;
39+
public String website = "www.cnblogs.com/chanshuyi";
40+
```
41+
42+
- **初始化的类型。**在准备阶段,JVM 会为类变量分配内存,并为其初始化。但是这里的初始化指的是为变量赋予 Java 语言中该数据类型的零值,而不是用户代码里初始化的值。
43+
44+
例如下面的代码在准备阶段之后,sector 的值将是 0,而不是 3。
45+
46+
```
47+
public static int sector = 3;
48+
```
49+
50+
但如果一个变量是常量(被 static final 修饰)的话,那么在准备阶段,属性便会被赋予用户希望的值。例如下面的代码在准备阶段之后,number 的值将是 3,而不是 0。
51+
52+
```
53+
public static final int number = 3;
54+
```
55+
56+
之所以 static final 会直接被复制,而 static 变量会被赋予零值。其实我们稍微思考一下就能想明白了。
57+
58+
两个语句的区别是一个有 final 关键字修饰,另外一个没有。而 final 关键字在 Java 中代表不可改变的意思,意思就是说 number 的值一旦赋值就不会在改变了。既然一旦赋值就不会再改变,那么就必须一开始就给其赋予用户想要的值,因此被 final 修饰的类变量在准备阶段就会被赋予想要的值。而没有被 final 修饰的类变量,其可能在初始化阶段或者运行阶段发生变化,所以就没有必要在准备阶段对它赋予用户想要的值。
59+
60+
### 解析
61+
62+
**解析过程就是查找类的常量池中的类,字段,方法,接口的符号引用,将他们替换成直接引用的过程。**
63+
64+
两个重点:
65+
66+
- **符号引用**。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
67+
- **直接引用**。可以理解为一个内存地址,或者一个偏移量。比如**类方法,类变量**的直接引用是指向方法区的**指针**;而**实例方法,实例变量**的直接引用则是从实例的头指针开始算起到这个实例变量位置的**偏移量**
68+
69+
### 初始化(重点)
70+
71+
这个阶段主要是对**类变量**初始化,是执行类构造器的过程。
72+
73+
- 换句话说,只对static修饰的变量或语句进行初始化。
74+
- 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
75+
- 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
76+
77+
78+
79+
到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。在这个阶段,JVM 会根据语句执行顺序对类对象进行初始化,一般来说当 JVM 遇到下面 5 种情况的时候会触发初始化:
80+
81+
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
82+
- 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
83+
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
84+
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
85+
- 当使用 JDK1.7 动态语言支持时,如果一个 java.lang.invoke.MethodHandle实例最后的解析结果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
86+
87+
### 使用
88+
89+
当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。
90+
91+
### 卸载
92+
93+
用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。
94+
95+
## 总结
96+
97+
从上面几个例子可以看出,分析一个类的执行顺序大概可以按照如下步骤:
98+
99+
- **确定类变量的初始值。**在类加载的准备阶段,JVM 会为类变量初始化零值,这时候类变量会有一个初始的零值。如果是被 final 修饰的类变量,则直接会被初始成用户想要的值。
100+
- **初始化入口方法。**当进入类加载的初始化阶段后,JVM 会寻找整个 main 方法入口,从而初始化 main 方法所在的整个类。当需要对一个类进行初始化时,会首先初始化类构造器(),之后初始化对象构造器()。
101+
- **初始化类构造器。**JVM 会按顺序收集类变量的赋值语句、静态代码块,最终组成类构造器由 JVM 执行。
102+
- **初始化对象构造器。**JVM 会按照收集成员变量的赋值语句、普通代码块,最后收集构造方法,将它们组成对象构造器,最终由 JVM 执行。
103+

0 commit comments

Comments
 (0)