类加载器
我们编写的.java文件被编译器编译成.class的字节码文件,类加载器ClassLoader负责将这些字节码文件加载到内存中去执行。
JVM提供了三种类加载器:
引导类加载器Bootstrap:最顶层类加载器,负责加载JDK核心类库,C++语言实现
扩展类加载器ExtClassLoader:负责加载JDK扩展类库,Java语言实现
应用类加载器AppClassLoader:负责加载我们自己定义的类和第三方jar包中的类,Java语言实现
AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器是Bootstrap。
双亲委派模型
双亲委派机制:当一个类加载器收到类加载的请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器完成,每个类加载器都是如此,因此所有的加载请求最终都会传送到顶层的引导类加载器中,只有当父加载器在自己的搜索范围内找不到指定的类时,子加载器才会尝试自己去加载。如下图所示:
加载过程:
当应用类加载器接到加载某个类的任务时,例如:java.lang.String
(1)会先在内存中搜索这个类是否加载过,如果是,就返回这个类的Class对象,不去加载。
(2)如果没有找到,即没有加载过,会把这个任务提交给父加载器。
当扩展类加载器接到类加载的请求时
(1)会先在内存中搜索这个类是否加载过,如果是,就返回这个类的Class对象,不去加载。
(2)如果没有找到,即没有加载过,会把这个任务提交给父加载器。
当引导类加载器接到类加载的请求时
(1)会先在内存中搜索这个类是否加载过,如果是,就返回这个类的Class对象,不去加载。
(2)如果没有找到,即没有加载过,去它负责的范围内尝试加载。
① 如果能够加载,那么就自己加载并返回这个类的Class对象,结束。
② 如果不能加载,那么会把这个任务往回传,让子加载器去加载。
扩展类加载器接到父加载器返回的任务后,去它负责的范围内尝试加载。
① 如果能够加载,那么就自己加载并返回这个类的Class对象,结束。
② 如果不能加载,那么会把这个任务往回传,让子加载器去加载。
应用类加载器接到父加载器返回的任务后,去它负责的范围内尝试加载。
① 如果能够加载,那么就自己加载并返回这个类的Class对象,结束。
② 如果不能加载,那么就抛出ClassNotFoundException
为什么要用这种类加载机制
你可能会想到两个问题:
(1)为什么不用就近原则,子加载器能够加载就让子加载器加载好了,却要委托给父加载器加载?
(2)检查一个类是否加载过为什么要重复搜索,既然加载请求最终要传给顶层的引导类加载器,那么为什么不一开始就交给顶层的引导类加载器,自顶向下尝试加载?
对于问题(1):保证程序的安全性,防止系统级别的类被替换。例如类java.lang.Object,最终要委派给处于模型最顶层的引导类加载器进行加载,它会优先jdk中提供的Object类,而不是我们自己定义的。
验证:我们自己定义一个Object类,包名也为java.lang:
package java.lang;
public class Object {
public static void main(String[] args) {
System.out.println("自定义的Object类");
}
}
发现运行报错:
明明在String类中定义了main方法,为什么提示找不到呢?因为实际加载的是核心类库中Object类,里面是没有main方法的。
对于问题(2):防止同一个类被重复加载。每层类加载器都有自己的确认逻辑,同一个类被不同的类加载器加载,会被识别为不同的类。只有每层的类加载器都确认自己没有加载过,才能最终判定这个类没有被加载。