虚拟机类加载机制
背景
今天看了深入java虚拟机中的虚拟机类加载机制一章,现对主要知识点进行总结和摘抄
类加载的时机
类的整个生命周期包含七个阶段:加载、验证、准备、解析,初始化、使用和卸载。其中验证、准备和解析阶段又可以统称为连接。
在上述这7个阶段中,加载、验证、准备、初始化和卸载这5个阶段是按顺序执行的,其中,按顺序不是指等一个阶段执行完才能执行下一个阶段,而是指各阶段开始执行是按顺序的,在执行过程中有可能是交错进行的。
什么时候开始进行类加载过程的第一个阶段:加载,虚拟机规范中并没有强制约束。但是规定了有且只有下面5中情况必须立即进行初始化
- 遇到new、getstatic、putstatic和invokestatic这4条字节码指令
- 使用java.lang.reflect包的方法对类进行反射调用时
- 初始化一个类时,若其父类未进行初始化,需先触发其父类初始化
- 虚拟机启动是,包含main方法的类,需初始化
- 当使用jdk1.7时的动态语言支持时,如果一个MethodHandle的实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的的类没有进行过初始化,需先触发其初始化。
下面分别就加载、验证、准备、解析和初始化这5个节点的具体动作进行介绍。
加载
在加载阶段,虚拟机需要完成下面3项工作:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的的Class对象,作为方法区这个类的各种数据的访问入口
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证阶段主要完成下面4个阶段的校验动作:
1、文件格式验证
主要是验证魔数、主次版本号是否在虚拟机处理的范围内等
2、元数据验证
主要是对字节码描述的信息进行语义分析,包括是否有父类、父类是否允许被继承,是否实现了父类或者接口中要求实现的方法,类中字段是否和父类中的冲突等
3、字节码验证
该阶段是整个验证阶段最复杂的一个阶段,这个阶段将对类的方法体进行校验分析,保证方法在运行时不会做出危害虚拟机安全的事件
4、符号引用验证
该验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段发生
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。注意:这里指指的是类变量,如下所示
public static int value = 123;
并且这个时候设置的初始值是0,不是123。将value赋值为123是在初始化阶段才会执行的。
解析
解析阶段是讲虚拟机常量池中的符号引用替换为直接引用的过程,关于两者的关系如下所述:
- 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要可以无歧义的定位到目标即可
- 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
初始化
初始化阶段是执行类构造器<client>()的过程,下面对<client>()进行讲解
1、<client>()是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并生成的。
2、虚拟机在保证子类的<client>()执行之前,父类的<client>()的方法已经执行完毕
3、<client>()方法对于类或者接口不是必须的
4、虚拟机会保证一个类的<client>()方法在多线程环境下被正确的加锁、同步和执行。
类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一起共同确立其在虚拟器中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗些说:比较两个类是否相等,只有这两个类是由同一个类加载器加载的前提下才有意义。
双亲委派模型
通常情况下,都会用到系统提供的3中类加载器
1、启动类加载器(Bootstrap ClassLoader):负责将<JAVA_HOME>\lib目录下的类库加载到虚拟机内存中
2、扩展类加载器(Extension ClassLoader):负责将<JAVA_HOME>\lib\ext目录中的类库
3、应用程序类加载器(Application ClassLoader):负责加载用户类路径上所指定的类库
用户也可以定义自己的类加载器,这些类加载器之间有如下关系:
从上图可以看出:除了顶层类加载器外,其余的类加载器都应当有自己的父类加载器
双亲委派模型的工作工程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成,每一层的父类加载器都是如此。只有当父类加载器反馈自己无法完成这个加载请求时,子类加载器材会尝试自己去完成。
使用双亲双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。并且能够保证类的唯一性。