虚拟机把描述类的数据从Class文件加载到内存,而且对数据进行校验、转换解析和初始化,最终造成能够被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
编译时无需进行链接工做,类的加载、链接和初始化过程都是在程序运行期间完成的。如面向接口的应用程序能够等到运行时再指定其实际的实现类;用户能够经过预约义或者自定义的类加载器,让本地的应用程序能够在运行时从网络或者其余地方加载一个二进制流做为程序代码的一部分。数组
类从被加载到虚拟机内存中开始,到卸载出内存为止,生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。安全
5种状况必须当即对类进行初始化:网络
过程主要包括加载、验证、准备、解析、初始化这5个阶段数据结构
加载阶段,虚拟机主要完成下面3件事情:函数
经过类的全限定名获取类的二进制字节流有多种方式,如:布局
非数组类可使用系统提供的引导类加载器完成,也能够用户自定义类加载器(重写类加载器的loadClass()方法)
数组类不经过类加载器建立,由虚拟机直接建立。spa
验证是链接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流种包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。线程
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区种进行分配。翻译
解析阶段是虚拟机将常量池内的符号引用替换位直接引用的过程,符号引用在Class文件种以CONSTSNT_Class_info、CONSTSNT_Fieldref_info、CONSTSNT_Methodref_info等类型的常量出现。
符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号能够是任何形式的字面量,只要使用时能无歧义的定位到目标便可。
直接引用(Direct References):直接引用能够是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不一样虚拟机实例上翻译出来的直接引用通常不会相同,若是有直接引用,那引用的目标一定已经在内存中存在。
类或接口的解析
假设类为D,要把一个从未解析过的符号引用N解析为一个类或者接口C的直接引用,解析过程分为三步:
1)C不是数组类型,虚拟机把N的全限定名传递给D的类加载器去加载类C,过程当中一旦出现异常,解析宣告失败。
2)C是数组类型且元素类型是对象,按照1的规则加载数组元素类型,接着由虚拟机生成一个表明此数组维度和元素的数组对象。
3)上面两步没有任何异常,C在虚拟机中已经成为一个有效的类或者接口,可是解析完成前还要进行符号引用验证,确认D是否具有对于C的访问权限。
字段解析
解析字段符号引用,首先对字段表内class_index中索引的CONSTANT_Class_info符号引用进行解析,就是类或者接口符号引用,若是过程当中发生异常会致使解析失败,若是成功将这个字段所属的类或者接口以C表示,后续操做为:
1)C自己包含简单名称和字段描述符都与目标匹配的字段,返回这个字段直接引用查找结束
2)若是C实现了接口,会按照继承关系从下往上查找,若是找到直接引用查找结束
3)若是C不是java.lang.Object,按照继承关系从下往上查找,若是找到直接引用查找结束
4)都没找到,抛出java.lang.NoSuchFieldError。若是找到还要判断是否具备访问权限
类方法解析
和字段解析类似,咱们假设方法所属的类解析成功为C,后续操做:
1)若是在类方法表中发现class_index中索引的C是个接口,就抛出java.lang.IncompatibleClassChangeError,也就是类的方法不能是一个接口方法或者抽象方法,是必须实现的。
2)若是第一步没问题,就在类C中查找是否有简单名称和描述符都与目标匹配的方法,若是找到直接引用查找结束
3)不然在C的父类中递归查找,若是找到直接引用查找结束
4)不然在C的接口列表和父接口中递归查询,若是找到说明C是一个抽象类,查找结束抛出java.lang.AbstractMethodError异常。
5)都没有找到,抛出java.lang.NoSuchMethodError,若是找到还要判断是否具备访问权限
接口方法解析
和类方法类似,设定方法所属接口解析成功为C,后续操做:
1)若是发现class_index的索引C是一个类而不是接口,就抛出java.lang.IncompatibleClassChangeError
2)在接口C中直接查找,若是找到直接引用查找结束
3)在接口C的父接口中递归查找,直到java.lang.Object类,若是找到直接引用查找结束
4)都没有找到,抛出java.lang.NoSuchMethodError,接口的方法默认都是public,所以不具备访问权限问题
类初始化是类加载过程的最后一步,这里开始执行类中定义的Java程序代码(或者说字节码)。初始化阶段是执行类构造器
虚拟机把类加载阶段中的“经过一个类的全限定名来获取描述此类的二进制字节流”这个动做放到Java虚拟机外部去实现,让应用程序字节决定如何去获取所须要的类,实现这个动做的代码模块称为“类加载器”。
任意一个类都要由加载他的类加载器和这个类自己一同确立其在Java虚拟机中的惟一性,每个类加载器都拥有一个独立的类名称空间。两个类是否相等,须要来源于同一个Class文件,被同一个虚拟机加载,同一个类加载器。
Java虚拟机只存在两种不一样的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其余全部的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,而且所有都继承自抽象类java.lang.ClassLoader。
根据更细致的分法,绝大部分Java程序都会使用到如下3种系统提供的类加载器。
类加载器的双亲委派模式(Parents Delegation Model)是指除了顶层的启动类加载器外,其他的类加载器都应当由本身的父类加载器,类加载器之间的父子关系通常不会以继承(Inheritantce)的关系来实现,而是都使用组合(Composition)的关系来复用父加载器的代码。
双亲委派模型的工做过程是:若是一个类加载器收到类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成,全部的加载请求最终都传送到顶层的启动类加载器中,只有父加载器没法完成加载请求(它的搜索范围中没有找到所须要的类),子加载器才会尝试本身去加载。
这样Java类随着它的类加载器一块儿具有了一种带有优先级的层次关系,例如java.lang.Object,它在rt.jar中,不管哪一个类加载器要加载这个类最终都是交给顶层启动类加载器进行加载,这样Object类在全部的加载器环境中都是同一个类。