小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
前言:最近非常不在状态,根本不能安心学习。小伙伴们千万不要像我这样……
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块成为“类加载器”。
类加载器虽然只用于实现类的加载动作,但它在 Java 程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java虚拟机中的唯一性。每一个类加载器,都拥有一个独立的类名称空间,不同的类加载器加载的类肯定是不相等的。
这里的相等包括 class 对象的 equals 方法, isInstance 方法的返回结果。
双亲委派模型
从 Java 虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++语言实现(只限于 HotSpot),是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由 Java 语言实现,独立于虚拟机外部,并且全部继承抽象类 java.lang.ClassLoader。
从 Java 开发人员的角度看,类加载器还可以划分的更细致一些,绝大部分 Java 程序都会使用到以下 3 种系统提供的类加载器。
启动类加载器 Bootstrap ClassLoder
这个类加载器主要负责加载 JAVA_HOME/lib 目录中的 jar 包,而且是根据文件名来识别的,名字不符合即使在里面也不会加载到虚拟机内存中。开发者不能直接使用启动类加载器。
扩展类加载器 Extension ClassLoader
这个加载器负责加载 JAVA_HOME/lib/ext 目录中的类库,开发者可以直接使用扩展类加载器。
应用程序类加载器 Application ClassLoader
由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,所以也称为系统类加载器。它负责加载用户类路径上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有定义过自定义的类加载器,一般情况下这个就是程序中默认的类加载器。
我们的程序都是由这 3 种类加载器相互配合进行加载的,如果有必要,可以加入自己定义的类加载器。
如图展示的是类加载器之间的层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不会以继承的关系来实现,而是都使用组合关系来复用父加载器的代码。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己加载,先丢给父加载器加载,所以所有的请求都应该传给启动类加载器加载,当父加载器无法完成加载的时候,子加载器才会尝试自己去加载。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是 Java 类随着它的类加载器一起具备了一种带有优先级的层级关系。例如类 java.lang.Object,它存在 rt.jar 之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。
双亲委派模型并不是一个强制性的约束模型,而是 Java 设计者推荐给开发者的类加载器实现方式。在 Java 的世界中大部分的类加载器都遵循这个模型,但也有例外,到目前为止,双亲委派模型主要出现过 3 次较大规模的“被破坏”情况。注意,这里的被破坏,是指为了实现某种目的,而不遵循双亲委派模型的例子。
双亲委派模型第一次被破坏是发生在双亲委派模型出现之前,java.lang.ClassLoader 在 JDK1.0 时就已经出现,而 JDK1.2 才有双亲委派模型,为了向上兼容之前的用户自定义的类加载器,JDK1.2 在 ClassLoader 中新添加一个方法 findClass(),用于添加自定义加载器的逻辑。当父加载器中找不到对应的类时会调用子类的 findClass() 方法,以满足双亲委派模型。
双亲委派模型的第二次被破坏是由于模型本身的缺陷,我们知道,双亲委派模型很好的解决了各个加载器的基础类的统一问题,因为基础类不管哪个加载器加载,最后都会是启动类加载器来加载 lib 下的 jar 包,那么,假如我们在基础类中调用了用户自定义的类(调用第三方厂商实现的接口,简称 SPI),而启动类加载器又不认识这些类,该怎么办呢?
Java 团队设计了一个线程上下文类加载器,这个加载器默认就是应用程序类加载器,有了这个加载器,需要调用 SPI 时就可以使用这个加载器来加载。这样也就是通过启动类加载器来调用子加载器实现加载,实际上和双亲委派模型是相反的,但这是没有办法的事。
Java 中所涉及 SPI 的加载动作基本上都采用这种方式,例如 JNDI、JDBC、JBI。
双亲委派模型第三次被破坏是由于用户对程序动态性的追求而导致的,这里说的动态性指的是当前一些非常热门的名词,代码热替换,模块热部署等。说白了就是希望应用程序能像我们的计算机外设那样,接上鼠标、U 盘不用重启机器就能立即使用。对于系统来说,就是当某个模块有问题时直接换一个模块,而不用重启系统。
OSGI(Open Service Gateway Initiative)技术是 Java 动态化模块化系统的一系列规范。它实现了 Java 模块化开发的标准,但是在 OSGI 环境下,类加载器不再是双亲委派模型中的树形结构,而是进一步发展为更加复杂的网状结构。
到这里,我们就说完了虚拟机类加载过程以及类加载器的全部内容。
推荐阅读: