前提
最近在做一个基础组件项目刚好需要用到JDK中的资源加载,这里说到的资源包括类文件和其他静态资源,刚好需要重新补充一下类加载器和资源加载的相关知识,整理成一篇文章。
理解类的工作原理
这一节主要分析类加载器和双亲委派模型。
什么是类加载器
虚拟机设计团队把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到了Java虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类,而实现这个动作的代码模块称为"类加载器(ClassLoader)"。
类加载器虽然只用于实现类加载的功能,但是它在Java程序中起到的作用不局限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立类在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类命名空间。上面这句话直观来说就是:比较两个类是否"相等",只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这个两个类是来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类必然"不相等"。这里说到的"相等"包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用instanceOf关键字做对象所属关系判定等情况。
类和加载它的类加载器确定类在Java虚拟机中的唯一性这个特点为后来出现的热更新类、热部署等技术提供了基础。
双亲委派模型
从Java虚拟机的角度来看,只有两种不同的类加载器:
- 1、第一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++编程语言实现,是虚拟机的一部分。
- 2、另一种是其他的类加载器,这些类加载器都是由Java语言实现,独立于虚拟机之外,一般就是内部于JDK中,它们都继承自抽象类加载器java.lang.ClassLoader。
JDK中提供几个系统级别的类加载器:
- 1、启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在${JAVA_HONE}\lib目录中,或者被XbootstrapPath参数所指定的目录中,并且是虚拟机基于一定规则(如文件名称规则,如rt.jar)标识的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,开发者在编写自定义类加载器如果想委派到启动类加载器只需直接使用null替代即可。
- 2、扩展类加载器(Extension ClassLoader):这个类加载器由sun.misc.Launcher的静态内部类ExtClassLoader实现,它负责加载${JAVA_HONE}\lib\ext目录中,或者通过java.ext.dirs系统变量指定的路径中的所有类库,开发者可以直接使用此类加载器。
- 3、应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher的静态内部类AppClassLoader实现,但是由于这个类加载器的实例是ClassLoader中静态方法
getSystemClassLoader()中的返回值,一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自实现的类加载器,一般情况下这个系统类加载器就是应用程序中默认使用的类加载器。 - 4、线程上下文类加载器(Thread Context ClassLoader):这个在下一小节"破坏双亲委派模型"再分析。
Java开发者开发出来的Java应用程序都是由上面四种类加载器相互配合进行类加载的,如果有必要还可以加入自定义的类加载器。其中,启动类加载器、扩展类加载器、应用程序类加载器和自定义类加载器之间存在着一定的关系:

上图展示的类加载器之间的层次关系称为双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的类加载器(Java中顶层的类加载器一般是Bootstrap ClassLoader),其他的类加载器都应当有自己的父类加载器。这些类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是通过组合(Composition)的关系实现。类加载器层次关系这一点可以通过下面的代码验证一下:
public class Main { public static void main(String[] args) throws Exception{ ClassLoader classLoader = Main.class.getClassLoader(); System.out.println(classLoader); System.out.println(classLoader.getParent()); System.out.println(classLoader.getParent().getParent()); } } //输出结果,最后的null说明是Bootstrap ClassLoader sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@
