加载流程
JVM类加载器加载流程如下:
步骤 | 操作 |
1 | 编译, 使用命令 java HelloWorld.java 生成 HelloWorld.class |
2 | 加载, 在硬盘查找并读取HelloWorld.class字节码文件,加载这个类 |
3 | 验证, 验证字节码的准确性 |
4 | 解析, 将符号引用替换为直接引用 |
5 | 初始化, 对类的静态变量初始化为指定的值,执行静态代码块 |
可以用画图表示如下:
双亲委派机制
双亲委派机制是当类加载器需要加载某一个.class字节码文件时,则首先会把这个任务委托给他的上级类加载器,递归这个操作,如果上级没有加载该.class文件,自己才会去加载这个.class。这是一种任务委派模式。
原理:
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终到达顶层的启动类加载器;
- 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
说的不那么拗口一点,向上依次查找可用加载器,直到找到顶层加载器也可用。
类加载器分类:
序号 | 加载器 | 加载内容 |
1 | 用户自定义加载器 | 自定义目录 |
2 | AppClassLoader | 环境变量 (用户定义的类或者第三方api类) |
3 | ExtClassLoader | jre/lib/ext 扩展 (加载-Djava.ext.dirs选项指定的目录) |
4 | BootstrapClassLoader | jre核心库 (如HaspMap类、java.lang.String类) |
如图所示:
其中用户自定义加载器为最底层加载器,它的上级是AppClassLoader加载器,再上级是ExtClassLoader加载器,最上层加载器是BootstrapClassLoader加载器。
双亲委派机制的好处
使用双亲委派机制的好处是可以避免类的重复加载,保护程序安全,防止核心API被随意篡改例如自定义类java.lang.string。
例子:
自定义一个String类java.lang.String
package java.lang;
public class String {
public String toString() {
return "hello";
}
}
调用java.lang.string
public class HelloWorld {
public static void main(String[] args) {
String s = new String();
System.out.println("s类加载器" + s.getClass().getClassLoader());
System.out.println("输出的字符串" + s.toString());
}
}
输出s类加载器null
输出的字符串
输出的字符串
输出的字符串没有我们想看到的hello字符串。调用的还是系统类库。String的类加载器使用的是BootstrapClassLoader,对Java不可见,所以返回了null 。
因为jvm类加载器是自底向上查找到的。最终还是会在BootstrapClassLoader加载器中加载java.lang.String。