深入理解JAVA虚拟机系列之类加载器

一、简介

  • 本文主要介绍类加载器和双亲委派模型相关知识。

二、类加载器

2.1 原理

  • java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码被称为“类加载器”;

  • 对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间;

  • 比较俩个类是否相等,只有在这俩个类是由同一个类加载器加载的前提下才有意义,否则,即使这俩个类来源于同一个Class文件,被同一个java虚拟机加载,只要加载它们的类加载器不同,那这俩个类就必定不相等;

三、双亲委派模型

3.1 类加载器分类

  • 在java虚拟机的角度看,只存在俩种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader), 这个类加载器使用C++语言实现,是虚拟机自身的一部分,另一中就是其它所有的类加载器,这些类加载器都有Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader;

3.2 启动类加载器(Bootstrap Class Loader)

  • 启动类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是java虚拟机能够识别的(按照文件名识别,如rt.java、tools.jar,名字不符合的类库即使放在lib目录也不会被加载)类库加载到虚拟机的内存中;
  • 启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委培给以你盗垒加载器去处理,那直接使用null代替即可,

3.3 扩展类加载器(Extension Class Loader)

  • 扩展类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的,它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库;
  • 根据扩展类加载器这个名称,就可以推断出这时一种java系统类库的扩展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展java SE的共嗯,在JDK9之后,这种扩展机制被模块化带来的天然的扩展能力取代;
  • 由于类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件;

3.4 应用程序类加载器(Application Class Loader)

  • 应用程序类加载器由sun.misc.Launcher$AppClassLoader来实现,由于应用程序类加载器是ClassLoader类中的getSystemClassLoader()方法的返回值,所以有些场合也称它为系统类加载器;
  • 负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器;
  • 如果应用程序没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器;

3.5 原理

  • 如上图占式的各种类加载器之间的层次关系北城为类加载器的双亲委派模型(Parents Delegation Model), 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应由自己的父类加载器。这里所说的类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,二十通常使用组合(Composition)关系来服用父加载器的代码;
  • 工作流程: 如果一个类加载器受到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如期,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载;
  • 使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object,它存放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能保证是同一个类;
  • 双亲委派模型的实现全部集中在java.lang.ClassLoader的loadClass()方法之中,逻辑大概是 先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父加载器加载失败,抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载;

四、模块化系统下的双亲委派模型

4.1 平台类加载器(Platform Class Loader)

  • jdk9的模块化之后,扩展类加载器(Extension Class Loader)被平台类加载器取代。这其实是一个很顺理成章的变动,既然整个JDK都基于模块化进行构建(原来的rt.jar和tools.jar被拆分成数十个JMOD文件),其中的Java类库就已天然地满足了可扩展的需求,那自然无需再保留<JAVA_HOME>\lib\ext目录,此前使用这个目录或者java.ext.dirs系统变量来扩展jdk功能的机制已经没有继续存在的价值了,用来加载这部分类库的扩展类也完成了它的历史使命;
  • 平台类加载器和应用程序类加载器都不在派生自java.net.URLClassLoader,如果由程序直接依赖了这种继承关系,或者依赖了URLClassLoader类的特定方法,那代码很可能会在JDK9及更高版本中崩溃。现在启动类加载器、平台类加载器、应用程序类加载器全部继承于jdk.internal.loader.BuiltinClassLoader,在BuiltinClassLoader中是心啊了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理;
  • jdk9中虽然仍然维持着三层类加载器和双亲委派的架构,但类加载的委派关系也发生了变动。当平台及应用程序类加载器受到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载;
posted @ 2021-03-08 21:07  请叫我猿叔叔  阅读(114)  评论(0)    收藏  举报