加载->验证->准备->解析->初始化
1.加载
比如运行时计算生成,这种场景使用得最多的就是动态代理技术,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass来为特定接口生成形式为“*$Proxy”的代理类的二进制字节流。
加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序
2.验证
这是连接阶段的第一步,目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全,避免因为载入了有害的字节流而导致系统崩溃。
验证阶段大致完成下面4个检验动作:
2.1 文件格式验证
检验字节流是否是符合Class文件规范,比如是否以魔数0xCAFEBABE开头、版本号是否在当前虚拟机处理范围之内、常量池中是否有不被支持的常量类型等等
2.2 元数据校验
主要是对字节码描述的信息进行语义分析,比如这个类是否有父类(除了Object类没有外,其他都必须有)、如果这个类不是抽象类,是否实现了其父类或者接口之中要求实现的所有方法
2.3 字节码验证
主要是通过数据流和控制流进行分析,比如确保跳转指令不会跳转到方法体以外的字节码指令上面、确保方法体中的类型转换是有效的
但是通过字节码验证也不是代表它绝对没有问题,因为不能够准确检查出它是否能够在有效时间内结束运行。
2.4 符号引用验证
这个阶段的校验是发生在虚拟机将符号引用转化为直接引用的时候发生的,具体来说就是连接的解析阶段。比如能否在符号引用中通过字符串描述的全限定名找到对应的类
3.准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在堆内存(也可以说是方法区)中进行分配。
需要注意的是,首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,零值如图:
“通常情况”下初始值是零值,那相对的会有一些“特殊情况”:如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,假设上面类变量value的定义变为
public static final int value = 123
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据Constantvalue的设置将value赋值为123。
4.解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,有类或接口解析、字段解析、类方法解析、接口方法解析
5.初始化
初始化阶段是执行类构造器<clinit>()方法的过程。
下面是<clinit>()的产生过程:
结果为2
注:同一个类加载器下,一个Class(类型)只会初始化一次