Class装载
类装载的条件
Class只有在必须使用的时候才会被装载,Java虚拟机不会无条件地装载Class类型。Java虚拟机规定,一个类或者接口在初次”主动使用“前,必须要进行初始化。
主动使用的几种情况:
- 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化
- 当调用类的静态方法时
- 当使用类或接口的静态字段时(final常量除外)
- 当使用java.lang.reflect包中发的方法反射类的方法时
- 当初始化子类时,要求先初始化父类
- 作为启动虚拟机,含有main()方法的那个类
加载类
Java虚拟机完成以下工作
- 通过类的全限定名,获取类的二进制数据流
- 解析类的二进制数据流为方法区内的数据结构
- 创建java.lang.Class类的实例,表示该类型
获取类二进制数据流的方法
- 通过文件系统读入一个class后缀的文件,或者也可能读入JAR、ZIP等归档数据包,或者提取类文件
- 通过HTTP之类的协议通过网络进行加载
- 运行时生成一段Class的二进制信息
连接阶段
验证类
当类加载到系统之后,就开始连接操作,验证是连接操作的第一步,它的目的是保证加载的字节码是合法、合理并符合规范的。主要包括:格式检查、语义检查、字节码验证、符号引用验证
准备类
当一个类验证通过时,虚拟机就会进入准备阶段,在这个阶段,虚拟机就会为这个类分配相应的内存空间,并设置初始值。Java虚拟机类型变量默认初始值表如下:
类型 | 默认初始值 |
---|---|
int | 0 |
long | 0L |
short | 0 |
char | \u0000 |
boolean | false |
refrence | 0f |
double | 0 |
注:Java并不支持boolean类型,对于boolean类型,内部实现是int,由于int的默认值是0,所以boolean默认值是false
如果类存在final修饰常量字段,那么常量字段也会在准备阶段被附上正确的值,这个赋值属于Java虚拟机的行为,属于变量的初始化。事实上,在准备阶段,不会有任何代码被执行。
解析类
在准备阶段完成之后,就进入了解析阶段。解析阶段的工作就是将类、接口、字段和方法的符号引用转为直接引用。
符号引用就是一些字面量的引用,和虚拟机的内部数据结构和内存布局无关。
初始化
类的初始化是类装载的最后一个阶段。此时,类才会开始执行Java字节码。初始化阶段的重要工作时执行类的初始化方法<clinit>
。此方法是由编译器自动生成的,它是由类的静态成员赋值语句以及static语句块合并产生的。
但编译器不会为所有的类都产生<clinit>
初始化函数。如果一个类既没有赋值语句,也没有static语句块,那么生成的<clinit>
函数就为空,编译器不会为该类插入<clinit>
函数
<clinit>
的调用,也就是类的初始化,虚拟机会在其内部确保多线程环境中的安全性,也就是说,当多个线程试图初始化同一个类时,只有一个线程可以进入<clinit>
函数,而其他线程必须等待,如果之前的线程成功加载了类,则等在队列中国的线程就没有机会再执行<clinit>
函数了。正是由于这种带锁线程安全的,因此,在多线程环境下进行类初始化的时候,可能会引起死锁,并且这种死锁是很难发现的。
下一篇博文,我们将结合三个实例,来加深理解。
原文作者: shinerio
原文链接: https://shinerio.cc/2016/12/25/java/jvm/Class装载/
许可协议: 知识共享署名-非商业性使用 4.0 国际许可协议