Java-Note-类加载
2018-04-14
Java
- 类加载过程: 每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载
- 类加载过程分解:
- 加载:
- 通过类的完全限定名来获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构(从静态文件中转换到内存中)
- 在java堆中生成一个代表这个类的java.lang.Class对象, 作为方法区这些数据的访问入口
- 加载阶段完成后, 虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中, 然后在java堆中实例化一个java.lang.Class类的对象, 这个对象作为程序访问方法区中的这些类型数据的外部接口
- 验证: 目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求, 不会危害虚拟机自身安全. 主要包括四种验证:
- 文件格式验证: 验证class文件格式规范,例如: class文件是否已魔术0xCAFEBABE开头 , 主、次版本号是否在当前虚拟机处理范围之内等
- 元数据验证: 这个阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范要求。验证点可能包括:这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)、这个类是否继承了不允许被继承的类(被final修饰的)、如果这个类的父类是抽象类,是否实现了起父类或接口中要求实现的所有方法。
- 字节码验证: 进行数据流和控制流分析,这个阶段对类的方法体进行校验分析,这个阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如:保证访法体中的类型转换有效,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但不能把一个父类对象赋值给子类数据类型、保证跳转命令不会跳转到方法体以外的字节码命令上。
- 符号引用验证: 符号引用中通过字符串描述的全限定名是否能找到对应的类、符号引用类中的类,字段和方法的访问性(private、protected、public、default)是否可被当前类访问。
- 准备: 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的知识点,首先是这时候进行内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。其次是这里所说的初始值“通常情况”下是数据类型的零值, 但当字段前面有描述符final时, 在准备阶段JVM会直接将其值设置为常量
- 解析: 主要将常量池中的符号引用替换为直接引用的过程.
- 初始化: 类加载的最后阶段, 若该类具有超类, 则对超类也进行初始化, 对静态变量和成员变量赋值
- 加载:
- 类加载器: JVM设计者把类加载阶段中的”通过’类全名’来获取定义此类的二进制字节流”这个动作方法哦Java虚拟机外部去实现, 以便让应用程序直接决定如何去获取所需要的类. 实现这个动作的代码模块称为”类加载器”
- JVM角度分类
- 启动类加载器(Bootstrap ClassLoader), 由C++语言实现, 属于虚拟机自身的一部分
- 所有其它类加载器: 由Java语言实现, 独立于JVM外部, 且全部继承自抽象类java.lang.ClassLoader
- Java开发者角度分类
- 启动类加载器: 负责加载JAVA_HOME\lib目录中并且能被虚拟机识别的类库到JVM内存中,如果名称不符合的类库即使放在lib目录中也不会被加载。该类加载器无法被Java程序直接引用。
- 扩展类加载器: 该加载器主要是负责加载JAVA_HOME\lib\,该加载器可以被开发者直接使用。
- 应用程序类加载器: 该类加载器也称为系统类加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
- 双亲委派模型: 当类加载器收到类加载的请求时, 不是首先自己加载, 而是将这个请求委派给上一级的类加载器, 因此所有的请求都会被传送给启动类加载器, 只有当上层类加载器无法完成该加载请求时, 子加载器才会尝试自行加载
- 优点: Java类随着它的类加载器一起具备了一种带有优先级的层次关系. 例如java.lang.Object类,无论哪个类加载器去加载该类,最终都是由启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
这样就不会产生由不同的类加载器加载同一个类导致这个类对象不相等的情况 - 类加载实现过程的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 首先检查该name指定的class是否有被加载
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 如果parent不为null,则调用parent的loadClass进行加载
c = parent.loadClass(name, false);
} else {
// parent为null,则调用BootstrapClassLoader进行加载
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果仍然无法加载成功,则调用自身的findClass进行加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
- 优点: Java类随着它的类加载器一起具备了一种带有优先级的层次关系. 例如java.lang.Object类,无论哪个类加载器去加载该类,最终都是由启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
- JVM角度分类