类装载的条件

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>函数了。正是由于这种带锁线程安全的,因此,在多线程环境下进行类初始化的时候,可能会引起死锁,并且这种死锁是很难发现的。

下一篇博文,我们将结合三个实例,来加深理解。