理解Java的类加载机制过程
类加载的概述
虚拟机把描述类的数据从Class文件加载到内存,并对这些数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类加载的时机
类从被加载到虚拟机内存中开始,到卸载为止,整个生命周期包括:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸载(Unloading)7个阶段。
那么这些阶段在什么时候被完成呢,类的加载阶段虚拟机中并没有强行规定,这个可以交给虚拟机进行按需执行,但是对于初始化阶段是由严格意义的,虚拟机规范中严格定义了只有5种情况必须对类进行初始化,而加载,验证,准备肯定要在这之前就执行。虚拟机在这说明这5中情况时,使用的是有且仅有5种,这5种是对一个类的主动引用,别的情况都是被动引用,不会触发初始化。
- 遇到new,getstatic,putstatic或invokestatic这四条字节码指令时,如果类没有进行初始化,就行进行初始化操作,这4中场景分别是:使用new关键字实例化一个对象,读取或设置一个类的静态字段(被final修饰,在编译期已经把结果放入常量池的静态字段除外),调用一个类的静态方法时。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果没有初始化就先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还未初始化,则需要先触发其父类的初始化。
- 但虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个类。
- 但使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStaatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法所对应的类没有进行过初始化,则需要先触发其初始化。
加载
加载需要完成3件事情:
-
通过一个类的全限定名来获取定义此类的二进制字节流。
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
-
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
这一阶段是要保证Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。如果验证到输入字节流符合Class文件格式的约束,虚拟机会抛出一个java.lang.VerifyError异常或其子类异常。验证大致会完成以下四个阶段的检验:
- 文件格式验证:是否以魔术0xCAFEBABE;主次版本号是否在当前虚拟机处理范围之内;常量池的常量中是否有不被支持的常量类型;还有更多其他的验证,该阶段的验证都是基于字节流进行的,只有通过了该验证才会进入到方法区进行存储,所以后面的上3种验证都是基于方法区的存储结构进行的,不会再操作字节流。
- 元数据校验:这个类是否有父类;这个类是否继承了不允许继承的类;如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法;这一个阶段主要是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。
- 字节码验证:主要通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。对类的方法体进行校验。保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似在操作数栈放了一个int型数据,使用时却按照long类型来加载如本地变量表;保证跳转指令不会跳转到方法体以外的字节码指令上;保证方法体中的类型转换是有效的。
- 符号引用验证:是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,通常需要校验下列内容:符号引用中通过字符串描述的全限定名是否能够找到对应的类;符号引用中的类,字段,方法的访问性是否可以被当前类访问。
准备
正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区进行分配,此过程只分配类变量。此初始值只是该数据类型下的零值,比如一个int型静态变量是12,但是在准备阶段初始值是0,12是是在执行putstatic指令后才被赋值的;如果是final的静态变量,那么准备的初始值就是其初始值。
解析
是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用是指一组字面量来描述所引用的目标,这与虚拟机中内存布局无关,引用的目标不一定已经加载到内存;直接引用是指可以直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的一个句柄。
初始化
咋准备阶段已经对变量进行了一次系统需要的初始化,这个阶段就可以按照程序的定义进行初始化数据了。
类加载器
类的加载器是用于类的加载阶段的动作,但是不仅仅只有这一个作用,对于一个类,他的唯一性适合类的加载器同一确立的,每一个类加载器都有一个独立的类名称空间,意思是:比较两个类是否相等时,只有在这两个类是由同一个类的加载器加载的前提下才有意义,否则,即使两个类源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类必定不相等。