Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”。
对于任意一个类,都必须由加载它的类加载器和这个类本身一起确立其在Java虚拟机中的唯一性。每一个类加载器都拥有一个独立的类名称空间。
从虚拟机的角度来看,只有两种类型的类加载器:一种是启动类加载器(Bootstrap ClassLoader)这个类加载器使用C++语言实现,是虚拟机的一部分;另一种是其他所有的类加载器,这些类加载器都是Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader.
启动类加载器Bootstrap Class Loader
该类加载负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是虚拟机能够识别的类库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可。
扩展类加载器Extension Class Loader
这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径中所有的类库。
应用程序类加载器Application Class Loader
这个类加载器由sun.misc.Launcher$AppClassLoader来实现。它负责加载用户类路径ClassPath上所有的类库。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
JDK9之前的Java应用都是由这三种类加载器互相配合来完成的。
上图展示的各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”
双亲委派模型模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器。这里的父子关系不是以继承的关系来实现的,通常使用组和关系来复用父加载器的代码。
工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传递到最顶层的启动类加载器这,只有当父类加载器反馈自己无法完成这个加载请求,子加载器才会尝试自己去完成加载。
双亲委派模型的优点
提高安全性
假设我们使用一个第三方Jar包,该包中自定义了一个String类,并在其中加入了恶意代码,那么JVM在加载该类的时候执行恶意代码,如果有双亲委派模型,自定义的String类不会被加载,因为最顶层的类加载器会首先加载系统的java.lang.String类,而不会加载自定义的String类,防止了恶意代码的注入。
防止程序混乱
如果用户编写了一个java.lang.String的同名类,如果每个类加载器都自己加载的话,那么会出现多个String类,导致混乱。如果本加载器加载了,父加载器则不加载,那么以哪个加载的为准又不能确定,也增加了复杂度。
自定义类加载器
我们可以自定义类加载器,只需要继承ClassLoader抽象类,并重写findClass方法,如果需要打破双亲委派模型,需要重写loadClass方法
可以看出,它直接返回ClassNotFoundException,因此自定义类加载器必须重写findClass方法。
欢迎关注我的微信公众号,分享leetcode解题心得和Java后端的相关知识