要想真正理解java类的加载机制,分三个步骤去理解:
1,类什么时候加载
2,类的加载过程
3,用什么去加载类
以下分三部分去介绍java类的加载时机、类的加载过程、加载器。
1,类的加载过程
类的加载过程主要分为七个阶段,但主要的过程是加载、验证、准备、解析、初始化。
1.1,加载
在加载阶段,虚拟机需要完成的三件事:
1,通过一个类的全限定名来获取该类的二进制字节流;
2,将这个字节流的静态存储结构转换成jvm运行时数据区的数据结构;
3,生成一个代表这个类的java.lang.Class对象,作为方法区使用这些数据的入口。
1.2,连接
连接主要分为三步:验证、准备、解析
1.2.1,验证
验证的主要目的是为了确保Class文件中的字节流中所包含的信息是符合虚拟机要求的,并且不会危害虚拟机自身的安全。
验证主要分为以下四个动作:
1,文件格式验证
a,魔数验证,"0xCAFEBABE";
b,主次版本号是否在虚拟机处理范围内;
c,常量池的常量数据类型是否被支持;
等等。。
2,元数据验证
元数据验证是对字节码描述的信息进行语义分析,保证其描述的信息符合java语言规范的要求。
a,是否有父类;
b,是否继续了不允许继承的类;
c,如果该类不是抽象类,是否实现了父类所有要求实现的方法;
等等。。
3,字节码验证
字节码验证的主要目的是通过控制流和数据流分析,确认程序语义的合法性和逻辑性。
a,保证任何时候操作数栈的数据类型与指令代码序列的一致性;
b,跳转指令不会跳转到方法体以外的字节码指令上;
4,符号引用验证
符号引用验证主要是为了保证解析阶段能正常进行,如无法通过符号验证则抛出异常。
a,符号引用的类、字段、方法的访问性(public、private等)是否可被当前类访问;
b,指定类是否存在符合方法的字段描述符;
等等。。
1.2.2,准备
准备是连接阶段的第二步,主要为静态变量在方法区分配内存,被设置默认初始化值。
1.2.3,解析
解析是连接阶段的最后一步,将常量池里的符号引用转成直接引用的过程。
1.3,初始化
初始化是类的加载过程的最后阶段,主要根据程序中的赋值语句主动对变量进行赋值。
注意:
1,当有父类且父类未初始化时,先初始化父类
2,再进行子类初始化操作
2,类的加载时机
1,使用new去实例对象的时候;
2,读取或设置静态字段时候(不包括staic final);
3,调用类的静态方法的时候;
4,使用Class.forName(“xxxx”)对类进行反射调用的时候;
5,初始化一个类的时候,先初始化其父类(注:1,父接口除外,父接口在调用时候才会初始化;2,子类引用父类的静态变量,只会触发父类的初始化)
6,被表明启动类的类(包含main方法的类)要初始化;
7,当使用JDK1.7的动态语言支持时,如果一个java.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
以上情况称为对类的主动引用,只要有以上几种情况就需要对类初始化。
3,类加载器
jvm提供了以下三种系统的类加载器:
1,启动类加载器(Bootstrap ClassLoader):最顶层的类加载器,负责加载JAVA_HOME/lib目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别, 如rt.jar)的类。
2,拓展类加载器(Extension ClassLoader):负责加载JAVA_HOME/lib/ext目录 中的,或通过java.ext.dirs系统变量指定路径中的类库。
3,应用程序类加载器(Application ClassLoader):也叫做系统类加载器,可以通过getSystemClassLoader()获得,负责加载用户路径(classpath)上的类库。如果没有自定义类加载器,一般这个就是默认的类加载器。
双亲委派模型:
如果一个加载器接受到加载请求,他自己不会去加载这个请求,而是将这个请求委派给他的父类去加 载,这样一层一层传送,最终到达启动类加载器。
只有父类加载器无法加载这个请求时,子类加载器才会去加载这个请求。
双亲委派模型代码实现
1,首先检查类是否被加载,没有则调用父类加载器的loadClass方法;
2,如果父类加载器为空,则调用启动类加载器去加载
3,如父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的findClass() 方法。
loadClass()源码: