Java的动态类加载功能是由类加载器子系统处理。当它在运行时(不是编译时)首次引用一个类时,它加载、链接并初始化该类文件。
1.1 加载:类由此组件加载。启动类加载器 (BootStrap class Loader)、扩展类加载器(Extension class Loader)和应用程序类加载器(Application class Loader) 这三种类加载器帮助完成类的加载。1. 启动类加载器 – 负责从启动类路径中加载类,无非就是rt.jar。这个加载器会被赋予最高优先级。2. 扩展类加载器 – 负责加载ext 目录(jre\lib)内的类.3. 应用程序类加载器 – 负责加载应用程序级别类路径,涉及到路径的环境变量等etc.上述的类加载器会遵循委托层次算法(Delegation Hierarchy Algorithm)加载类文件,这个在后面进行讲解。
加载过程主要完成三件事情:
-
通过类的全限定名来获取定义此类的二进制字节流
-
将这个类字节流代表的静态存储结构转为方法区的运行时数据结构
-
在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。
1.2 链接:
- 校验 字节码校验器会校验生成的字节码是否正确,如果校验失败,我们会得到校验错误。
文件格式验证:基于字节流验证,验证字节流符合当前的Class文件格式的规范,能被当前虚拟机处理。验证通过后,字节流才会进入内存的方法区进行存储。
元数据验证:基于方法区的存储结构验证,对字节码进行语义验证,确保不存在不符合java语言规范的元数据信息。
字节码验证:基于方法区的存储结构验证,通过对数据流和控制流的分析,保证被检验类的方法在运行时不会做出危害虚拟机的动作。
符号引用验证:基于方法区的存储结构验证,发生在解析阶段,确保能够将符号引用成功的解析为直接引用,其目的是确保解析动作正常执行。换句话说就是对类自身以外的信息进行匹配性校验。
- 准备 – 分配内存并初始化默认值给所有的静态变量。
public static int value=33;
这据代码的赋值过程分两次,一是上面我们提到的阶段,此时的value将会被赋值为0;而value=33这个过程发生在类构造器的<clinit>()方法中。
- 解析所有符号内存引用被方法区(Method Area)的原始引用所替代。
举个例子来说明,在com.sbbic.Person类中引用了com.sbbic.Animal类,在编译阶段,Person类并不知道Animal的实际内存地址,因此只能用com.sbbic.Animal来代表Animal真实的内存地址。在解析阶段,JVM可以通过解析该符号引用,来确定com.sbbic.Animal类的真实内存地址(如果该类未被加载过,则先加载)。
主要有以下四种:类或接口的解析,字段解析,类方法解析,接口方法解析
1.3 初始化:这是类加载的最后阶段,这里所有的静态变量会被赋初始值, 并且静态块将被执行。
java中,对于初始化阶段,有且只有**以下五种情况才会对要求类立刻初始化:
-
使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类;
-
初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化;
-
使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化;
-
虚拟机启动时,用户会先初始化要执行的主类(含有main);
-
jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化;
1.Classloader 类结构分析
(1) 主要由四个方法,分别是 defineClass , findClass , loadClass , resolveClass
- <1>defineClass(byte[] , int ,int) 将byte字节流解析为JVM能够识别的Class对象(直接调用这个方法生成的Class对象还没有resolve,这个resolve将会在这个对象真正实例化时resolve)
- <2>findClass,通过类名去加载对应的Class对象。当我们实现自定义的classLoader通常是重写这个方法,根据传入的类名找到对应字节码的文件,并通过调用defineClass解析出Class独享
- <3>loadClass运行时可以通过调用此方法加载一个类(由于类是动态加载进jvm,用多少加载多少的?)
- <4>resolveClass手动调用这个使得被加到JVM的类被链接(解析resolve这个类?)
类加载器的双亲委派模型
当一个类加载器收到一个类加载的请求,它首先会将该请求委派给父类加载器去加载,每一个层次的类加载器都是如此,因此所有的类加载请求最终都应该被传入到顶层的启动类加载器(Bootstrap ClassLoader)中,只有当父类加载器反馈无法完成这个列的加载请求时(它的搜索范围内不存在这个类),子类加载器才尝试加载。其层次结构示意图如下: