JVM系列之类的准备与解析
一、前言
每个类的生命周期,和生命一样,都有起点与终点;
每个类都会在自己的生命周期里,完成自己的使命--使命必达!
类在这个阶段,被赋予了它的天赋与使命;
二、准备阶段
为class对象和类元数据信息分配内存空间,并初始化静态变量和常量值,这里变量使用的内存都在方法区中分配;
这里是初始化仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化随着对象一起分配在Java堆中;
这里所说的初始值“通常情况”下是数据类型的零值,例如:
public static int value=110;
这里value赋的初始值是0不是110,这时候尚未开始任何Java方法,而把value赋值为110的 putstatic 指令是程序被编译后,存放于类构造器初始化方法之中,所以把value赋值为110的动作将在初始化阶段才会执行;
java 基本类型零值如果变量是被final关键字修饰,那么准备阶段就会被赋值为110,不必等到初始化阶段再赋值。
三、解析
该阶段是将jvm虚拟机中常量池的符号引用替换成直接引用,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符。
1、类或接口的解析
第一步: 如果C不是一个数组类型,那么虚拟机会把代表N的权限定名传递给D的类加载器去加载这个类C。在加载的过程当中,由于元数据、字节码验证的操作,又可能触发其它类的加载动作,一旦出险任何异常,则解析宣告失败;
第二步:如果C是一个数组类型,并且数组元素为对象,描述符类似“[Ljava/lang/Integer”的形式按照第一点的规则加载数组元素类型。如果N的描述符如前面所假设的形式,需要加载的类型就是java.lang.Integer,接着由虚拟机生成一个代表次数组维度和元素的数组对象;
第三步:如果上面的步骤没有任何异常,那么C在虚拟机中实际上已经称为一个有效的类或接口了。解析之前还要进行符号验证,确认D是否具有对C的访问权限,如果不具备则会抛出异常。
2、字段解析
对字段表内class_index项中索引的CONSTANT_Class_info符号引用进行解析,也就是字段所属的类或接口的符号引用;
如果解析这个类或符号引用的过程中出现任何异常,都会导致字段符号引用解析的失败;
如果解析成功,接下来沿着它的父类/父接口寻找是否有这个字段;
如果这个过程不出错,则会在找到符合字段的时候返回这个字段的直接引用(如果有会进行权限验证,如果不具备权限则抛出异常)。
3、类方法解析
类方法解析首先也要首先解析出类方法表class_index项中索引的方法所属的类或接口的符号引用;
1)类方法和接口方法符号引用的常量类型定义是分开的,如果在类方法表中索引类是个接口,直接抛出异常;
2)如果通过了第一步,在类中查找是否有简单名称和描述符都与目标匹配的方法,有则返回这个方法的直接引用;
3)如果第二步没找到,则会在类的父类递归查找是否有这个方法,有则返回直接引用,或者在类的接口列表和父接口递归查找,如果存在匹配的方法,说明类是一个抽象类,抛出异常;
4)如果还是没有找到,就是说明该类不存在或不存在在已知路径中,报错抛异常。
4、接口方法解析
接口方法需要先解析出接口方法表的class_index 项中索引的方法所属的类或接口的符号引用;
如果在class_index发现有名称和描述匹配的方法,则查找成功,直接返回引用,否则就会在其父类接口中递归查找,如果还是找不到或者找到的是类,就直接抛异常。