JVM类加载分为七步
加载----验证----准备----解析—初始化—运行—卸载 其中验证准备解析 统称为连接过程。
在类加载中加载、验证、准备、初始化、卸载的开始顺序是固定的。而只有开始顺序是固定的,在运行和完成并不一定是按着这个顺序进行的。他们可以是混合这进行,但是开始顺序是按着这个顺序开始的。
jvm并没有强行规定加载的时间。但是初始化开始之前必须要进行加载、验证、准备。
强制初始化
- 遇到new、getstatic、putstatic、invokestatic关键字时如果类没有初始化,则必须先进行初始化。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,必须先初始化。
- 当初始化一个类的时候,发现他的父类没有初始化,必须先初始化他的父类。
- 当虚拟机启动的时候,用户需要指定一个执行的主类(含main()方法那个类)。虚拟机会先初始化这个类
- 使用JDK1.7的动态语言支持时,一个java.lang.invoke.MethodHandle实例后的解析结果REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄时,并这个句柄对应的类没有初始化是,需要先初始化句柄对应的类
有且只有这5种情况才会对类进行初始化。
加载
在加载阶段JVM会完成以下3件事:
- 通过类的全限定名获取对应的二进制字节流。
- 通过二进制字节流所代表的静态存储结构转换为对应的方法区的运行时结构。
- 在内存中生成一个代表该类的java.lang.Class对象,作为方法区该类的各个数据的访问入口。
加载阶段和连接阶段的部分内容是交叉进行的,可能加载阶段尚未结束,连接阶段就已经开始了,但是夹杂在加载阶段的内容同样是属于连接阶段的内容。而两个阶段的开始时间还是按着先后顺序开始的。
验证
验证阶段是为了保证JVM的安全,验证class文件的字节流的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证大致会完成4个方面的验证
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
文件格式验证:
验证字节流是否符合class文件的规范,并且能否被当前虚拟机处理。一般会包括下面几点:
- 是否以魔数0xCAFEBABE开头
- 主、次版本是否在当前虚拟机处理范围之内
- 常量池的常量是否有不被支持的常量类型
- 指向常量的各种索引值中是否有指向不存在的常量或者不符合类型的常量
- Class文件的各个部分及文件本身是否有被删除或者附加的其他信息
该验证是保证该字节流能否被正确的解析并且存放于方法区内,格式上符合描述一个java类型的要求。
元数据验证
对字节码描述的信息进行语义分析,保证其描述的信息符合Java语言规范。
- 这个类是否有父类(除了java.lang.Object之外所有类都应该有父类)
- 这个类的父类是否继承了不允许继承的类(被final修饰的类)
- 如果这个类不是抽象类,是否继承了其父类或者接口要求必须实现的方法
- 类中的字段、方法是否与父类发生了矛盾(覆盖父类的final字段,出现不符合的方法重载)。
- …
字节码验证(也是最复杂的一步)
通过数据流和控制流分析语义是否合法、是否符合逻辑
对类的方法体进行分析,保证被校验的类在运行的时候不会做出危害虚拟机安全的事件,例如:
- 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作(操作栈放一个int,使用时却按long类型加载本地变量)
- 保证跳转指令不会跳转到方法体之外的字节码指令上
- 保证方法体中的类型转换是有效的
- …
符号引用验证
发生在虚拟机将符号引用转换为直接引用的时候。(在解析阶段发生)符号引用可以当做是本身类之外的引用(常量池的各种符号引用)的信息进行匹配性校验。一般校验一下内容:
- 符号引用中能否通过字符串描述的全限定名找到对应的类
- 符号引用中的类、字段、方法是否能被当前类可见(private、public、protected、default)
- …
符号引用保证的是解析阶段是否能正常的运行。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区内分配。这时候进行内存分配的变量仅仅是类变量(被static修饰的变量),而不包括实例变量,实例变量将在java堆中分配。分配的也是数据的零值
public static int value = 123 ,在准备阶段后值也是 0 而不是 123 ,只有在初始化阶段后执行了putstatic指令后value才会赋值为123.
基本数据类型对应的零值
- int 0
- long 0L
- short (short)0
- char ‘\u000’
- byte (byte)0
- boolean false
- float 0.0f
- double 0.0d
- reference null
也有例外,如果字段属性上存在ConstantValue属性的话,准备阶段也会直接赋值。 一般就是final定义。
public static final int value = 123 ,这个字段在准备阶段就会定义为123
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。