目录
启动类加载器(Bootstrap ClassLoader):
扩展类加载器(Extension ClassLoader):
应用程序类加载器(Application ClassLoader):
各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”
在了解双亲委派模型之前,我们必须要知道:什么是类加载器?
类加载器
类加载器的定义:
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader)。
在Java虚拟机的角度看,存在两种不同的类加载器:
一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;
另一种是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
站在Java开发人员的角度来看,多种不同的类加载器:
启动类加载器(Bootstrap ClassLoader):
这是最顶层的类加载器,由 C/C++ 语言实现,是 JVM 自身的一部分。
它负责加载 JDK 中的核心类库,如 java.*、javax.* 等。
启动类加载器是无法被Java程序直接访问的。
扩展类加载器(Extension ClassLoader):
扩展类加载器是由 Java 语言实现的类加载器。
开发者可以直接使用扩展类加载器来加载自定义的类库。
扩展类加载器的父加载器是启动类加载器。
应用程序类加载器(Application ClassLoader):
应用程序类加载器也是由 Java 语言实现的类加载器。
它负责加载用户类路径上的所有类库。
开发者可以直接使用应用程序类加载器来加载自己编写的类。
应用程序类加载器的父加载器是扩展类加载器。
类与类加载器
两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
这里,在不同的类加载器的情况下,用instanceof关键字看一下结果,来了解上边这句话的含义
写一个简单的类加载器,它可以加载与自己在同一路径下的Class文件:
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("com.jilit.classLoad.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.jilit.classLoad.ClassLoaderTest);
}
}
运行结果:
两行输出结果中,从第一行可以看到这个对象确实是类com.jilit.classLoad.ClassLoaderTest实例化出来的,但在第二行的输出中却发现这个对象与com.jilit.classLoad.ClassLoaderTest做所属类型检查的时候返回了false。这是因为Java虚拟机中同时存在了两个ClassLoaderTest类,一个是由虚拟机的应用程序类加载器所加载的,另外一个是由自定义的类加载器加载的,虽然它们都来自同一个Class文件,但在Java虚拟机中仍然是两个互相独立的类,做对象所属类型检查时的结果自然为false。
双亲委派模型
定义:
各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”
双亲委派模型代码示例:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
// 首先,检查请求的类是否已经被加载过了
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出ClassNotFoundException
// 说明父类加载器无法完成加载请求
}
if (c == null) {
// 在父类加载器无法加载时
// 再调用本身的findClass方法来进行类加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
双亲委派模型的工作流程:
1、如果一个类加载器收到了类加载的请求,类加载器首先会检查该类是否已经被加载过。
2、如果已经被加载:直接返回该类的实例。
3、如果未被加载:它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,如果父类加载器能够成功加载该类,则返回该类的实例。
4、只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
双亲委派模型的作用:
1、防止类的重复加载:在双亲委派模型中,一个类只会被加载一次。这是因为如果一个类已经被父加载器加载过,后续子加载器再次请求加载该类时,都会直接返回父加载器已加载的那个类。
2、隔离应用程序:
双亲委派模型可以有效地隔离不同的Java应用程序(每个应用程序都有自己的应用程序类加载器,它们加载的类互不干扰)这样可以避免一个应用程序中的类影响到其他应用程序,提高了系统的安全性。
3、保护 Java 核心库:
双亲委派机制可以防止用户自定义的类覆盖Java核心库中的类,确保了Java核心API的安全性,防止恶意代码的植入。(因为高级别的启动类加载器和扩展类加载器会先尝试加载类,如果能够加载成功,就不会交给低级别的应用程序类加载器去加载。)
4、增加可扩展性:双亲委派模型很好地支持了Java类加载机制的可扩展性。开发者可以自定义类加载器,且各个类加载器之间互不干扰,形成了一个相对完整的类加载器体系。