概述
通过一个类的全限定名来获取描述该类的二进制字节流,实现这个动作的代码被称为“类加载器”(Class Loader)。这个动作可以放到Java虚拟机外部实现,以便让应用程序自己去决定如何去获取所需的类。
对于任何一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。每一个类加载器都有一个独立的类名称空间。
双亲委派模式
站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机的一部分;另一种就是他所在的类加载器,这些类加载器由Java语言实现,独立存在于虚拟机外部,并且全都继承抽象类java.lang.ClassLoader。
JDK 8 及之前的版本的Java,绝大多数Java会使用一下3个系统提供的类加载器来进行加载:
- 启动类加载器(Bootstrap Class Loader):这个类加载器负责加载在<JAVAJ_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存当中。启动类加载器无法被Java程序直接引用,用户编写自定义类加载器的时,如果需要把加载请求委派给引导类加载器去处理,那么直接使用null代替即可。
- 扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext\目录中,或被java.ext.dirs系统变量所指定的路径中所有的类库。
- 应用程序加载器(Application Class Loader):这个类加载器由sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystemClassLoader()方法来实现的返回值,所以有些场合也称它为“系统类加载器”。它负责加载用户类路径(ClassPath)上所有的类库。如果应用程序中没有定义类加载器,一般情况下这个就是应用程序默认的类加载器。
双亲委派模型(Parents Delegation Model)要求除了顶层类的启动类加载器之外,其余的类加载器都应该有自己的父类记载器。不过这种类加载器之间的父子关系不是通过继承的关系实现的,而是通过组合的方式来复用父加载器的方法。
双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的类加载请求最终都应该送到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子类加载器才会尝试自己去完成加载。
双亲委派模型如下图:
破坏双亲委派模型
双亲委派模型主要出现过3次较大规模“被破坏的情况”:
- 由于双亲委派模型是在JDK 1.2之后引入的,但是类加载器和抽象类java.lang.ClassLoader则在Java第一个版本就存在。面对已经存在的用户自定义的类加载代码,只能在JDK1.2之后的java.lang.ClassLoader中添加一个新的protected方法findClass(),并引导用户编写类加载逻辑时尽可能重新这个方法。
- 第二次“被破坏”主要是这个模型自身的缺陷,例如JNDI服务是Java的标准服务,它的代码是由启动类加载器来完成加载(在JDK1.3中加入到rt.jar的),但是它需要调用其它厂商并实现部署在应用程序的ClassPath下的JNDI服务提供接口的代码。为了解决这个困境,java引入了一种设计:线程上线问类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果线程创建时还未设置,他将会从父类线程中继承一个,如果应用程序全局范围内没有都没有设置过得话,那么这个类加载器默认就是应用程序类加载器。
- 第二次“被破坏”是由于用户多热部署的追求。OSGi实现模块化热部署的关键是它自定义了类加载机制实现,每一个程序块(OSGi中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起替换掉已实现代码的热替换。