概念:虚拟机把类的数据从class文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以直接被虚拟机使用的Java类型,这就是虚拟机的类加载机制。
类加载的时机
类加载的生命周期:加载—>验证-准备-解析—>初始化—>使用—>卸载
————————————
连接中
加载与连接阶段是交叉进行的
什么时候虚拟机必须对类进行初始化
1、遇到new、getStatic、putStatic或者invokeStatic字节码指令时
使用new关键字实例化对象的时候
读取或者设置一个类静态字段
调用一个的静态方法
2、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化
3、父类还没有初始化的时候,需要先触发其父类的初始化
4、虚拟机启动时,用户需要指定一个执行的主类,虚拟机会先初始化这个子类
5、JDK1.7后,如果MethodHandle实例最后解析结果为REF_getStatic,REF_putStatic、REF_invokeStatic的方法句柄,而这个方法对应的类还没有进行初始化,则先触发其初始化。
在invoke指令、反射、执行主类等动作之前,MethodHandle实例解析后,开始对类进行初始化
类加载的过程
加载
加载阶段:
1、通过一个类的全限定名来获取该类的二进制字节流
2、将这个字节流的静态存储结构转化为方法区的运行时数据结构
3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
数组类
对于数组类而言,数组类不通过类加载器创建,由jvm直接创建,但数组类的元素类型最终还是要靠类加载器去创建
1、组件类型是引用类,递归采用加载过程去加载这个组件类型,数组将在加载该类型的类加载器的类名称空间上被标记。
2、如果数组组件不是引用类,JVM将会把数组标记为与引导类加载器关联
3、数组类型的可见性与组件类型的可见性一致,如果组类类型不是引用类,那么默认可见行为public
验证
文件格式验证
是否符合class文件格式规范
元数据验证
对元数据信息进行语义分析,保证不存在不符合Java语言规范的元数据信息
字节码验证
通过数据流和控制流分析,验证程序语义是否合理,保证在运行时不会做出危害虚拟机安全的事件
符号引用验证
发生在将符号引用转换为直接引用的时候,这个转换动作将在解析时发生,保证解析动作能够正常运行。
准备
正式为类变量分配内存并设置类变量初始值的阶段,这些变量将在方法区中被分配。被分配的只包括类变量(被static修饰的变量),不包括实例变量,实例变量会在对象实例化的时候随着对象一起分配在Java堆中。
解析
虚拟机将常量池内的符号引用替换为直接引用的过程
符号引用:以一组符号来表示所引用的目标
与内存布局无关
引用的目标并不一定已经加载到内存中
直接引用:是直接指向目标的指针、相对偏移量、间接定位到目标的句柄
与虚拟机实现的内存布局相关
有了直接引用,引用目标必定已经在内存中
类或接口解析:
如果需要将一个符号引用N,解析为一个类或者接口C的直接引用,需要一下3个步骤:
1、如果C不是一个数组类型,那虚拟机会把代表N的权限定名传递给D的类加载器去加载这个类C,在加载过程中会触发其他类加载动作,例如加载父类或者是接口实现类,一旦异常,即解析失败。
2、如果C是一个数组类型,并且数组元素是以[Ljava.lang.Integer的形式,那么会按照第一条的规则来加载数组元素类型
3、如果上面的步骤没有出现任何异常,那么C在虚拟机中已经是一个有效的类或接口了,但在解析完成前,还需要进行符号引用验证,判断是否有权限进行访问。IlegalAccessError
字段解析:
类方法解析:
类方法解析的第一步,也是需要解析出类方法表的class_index项中索引的方法所属的类或者接口的符号引用,如果解析成功
1、类方法和接口方法的符号引用是分开的,如果发现类方法表中class_index索引是个接口,那么直接异常
2、在C中类查找是否有简单名称和描述相匹配的方法,如果有,则返回这个方法的直接引用,查找结束
3、在父类中查找是否有简单名称和描述相匹配的方法
4、在类C实现的接口列表及他们的父接口之中递归查找
5、否则宣告方法查找失败
接口方法解析:
1、与类方法相似,如果方法接口方法中class_index索引是个类,那么直接异常
2、在接口C中查找是否有简单名称和描述相匹配的方法
3、在接口C的父接口中递归查找,直到找到为止
4、接口中的所有方法都是默认public的,所以不存在访问权限的问题
初始化:
初始化阶段是执行<client>()方法的过程
类加载器
将“通过一个类的全限定名来获取类的二进制字节流“这个动作放到Java虚拟机外部实现,让程序自己去获取需要的类
双亲委派模型
启动类加载器:
1、这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者说是-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中
2、启动类加载器无法被Java程序调用
扩展类加载器:
1、负责加载<JAVA_HOME>\lib\ext的扩展类库
2、开发者可以直接使用
应用程序加载器:
1、负责加载用户路径(ClassPath)上指定的类库
2、开发者可以直接使用
3、应用程序中没有自定义过自己的类加载器,一般情况下,这个就是默认加载器
双亲委派模型的工作过程:
如果一个类加载器收到了类加载请求,它不会自己尝试加载这个类,而是把请求委派给父类加载器去完成,每层如此,只有当父类加载器反馈说无法查找到此类,才会尝试自己去加载