JVM - 类加载器 ClassLoader + 双亲委派机制 Parent-Delegation Model

1. 双亲委派模型

不是继承关系,而是委托关系。

从Java虚拟机的角度来讲,只存在两种不同的类加载器:

  • 一种是启动类加载器(Bootstrap ClassLoader),仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载。这个类加载器使用C++语言实现,是虚拟机自身的一部分;
  • 另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

 

 

 

启动类加载器(Bootstrap ClassLoader)

C++实现。这个类将器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。Bootstrap Classloader 无法被Java程序直接引用,因为java无法直接调用c++代码。用户在编写自定义类加载器时,如果需要把加载请求委派给bootstrap classloader,那直接使用null代替即可。

扩展类加载器(Extension ClassLoader)

这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器(Application ClassLoader)

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

 

2. 双亲委派模型的优点

使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。

 

例如:如果用户自己编写了一个java.lang.String的类(如下图,故意模拟rt.jar的java.lang.String),和rt.jar里的java.lang.String的路径一致。但是这里的main方法无法运行,会抛出错误“String类中没有main方法”。这是因为由于双亲委派模型,会先把这个String类委托给上层的类加载器加载,在最上层bootstrap classloader会找到rt.jar里的java.lang.String并加载到JVM中。所以自己写的java.lang.String并没有被加载进JVM。

 

 

 

3. 破坏双亲委派模型

双亲委派能够很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),基础类之所以成为基础,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美,如果基础类又要调用回用户的代码,那该怎么办呢

  

那jdk又是怎么做的呢?他们引入了:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.Lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

 

Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等,这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由bootstrap classloader来加载的;SPI的实现类是由Application ClassLoader来加载的。bootstrap classloader启动类加载器是无法找到 SPI 的实现类(存在于Application ClassLoader)的,因为依照双亲委派模型,BootstrapClassloader无法委派Application ClassLoader来加载类。有了线程上下文类加载器,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上已经打破了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则。

 

背景:

SPI(Service Provider Interface)模式

双亲委派模式破坏-JDBC

 

QA: 如何判断两个类是一致的?

 

 

 

QA: 如何写自定义的ClassLoader?

如何自定义类加载器,都要继承ClassLoader类

 

QA: 类和对象的区别

类是模板,同一个类class都是同一个类对象,hashcode都一样。

对象是具体的,同一个类的对象hashcode都是不一样的。

 

posted on 2020-12-15 00:27  frank_cui  阅读(125)  评论(0编辑  收藏  举报

导航

levels of contents