1. 类加载器
Java虛拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader)。
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都有一个独立的类名空间。
2. 双亲委派模型
自JDK1.2开始以来,Java一直保持着三层类加载器、双亲委派的类加载架构。
三层类加载器
启动类加载器(Bootstarp Class Loader)
这个类加载器负责加载存放在<JAVA_HOME>\lib目录下,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别.jar)类库加载到虚拟机内存中。
用户也可以自己加载,需要委派给引导类加载器去处理。
扩展类加载器(Extension Class Loader)
它负责加载<Java_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。
由于扩展类是由Java代码实现的,程序媛可以直接在程序中使用扩展类加载器来加载Class文件。
应用程序类加载器(Application Class Loader)
应用程序是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以有些场合也称它为:系统类加载器。
它负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。
双亲委派模型
各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”。
2.1. 工作过程
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
2.2. 优点
使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.langObiect,它存放在rt.jar之中,无论哪-个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Obiect类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为iava.langObject的类,并放在程序的ClassPath中,那系统中就会出现多个不同的Obiect类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。如果读者有兴趣的话,可以尝试去写一个与n.iar类库中已有类重名的Java类,将会发现它可以正常编译,但永远无法被加载运行。
2.3. 破坏双亲委派模型
双亲委派模型并不是一个具有强制性约束的模型,而是Java设计者推荐给开发者们的类加载器实现方式。在Java世界中大部分类加载器都遵循这个模型,但也有例外的情况,在模块化(Java9)出现之前,双亲委派模型主要出现过3次较大规模“被破坏”的情况。
第一次破坏
为了避免loadClass()被子类覆盖的可能性,于是在JDK1.2之后的Java.lang.ClassLoader中新添加一个protected方法findClass(),并引导用户编写类的加载逻辑时尽可能的去重写这个方法,而不是在ClassLoader()中编写代码。
第二次破坏
原因是因为这个“双亲委派功能”自身的缺陷导致的。
双亲委派很好的解决了各个类加载器协作时基础类型的一致性问题(越基础的类由越上层的加载器进行加载),但是我基础类型又要回调用户的代码怎么办?
第三次破坏
双亲委派机制的第三次破坏是由于用户对程序动态性的追求而导致的:代码热替换、模块热部署
这里比较特点的就是IBM的OSGI。
OSGI实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块(OSGI中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。