目的

为了防止内存中存在多份同样的字节码,保证Java核心库的安全性。

类加载器

  • 引导类(根)加载器(Bootstrap ClassLoader):加载Java核心库(jre/lib/rt.jar),同时加载另外两个类加载器,由C++编写;
  • 扩展类加载器(Extensions ClassLoader):加载Java的扩展库(jre/ext/*.jar);
  • 应用类加载器(Application ClassLoader):根据Java应用的类路径(classpath)来加载Java类。一般来说,Java应用的类都有它来完成加载。

image.png

类加载器规则

如果一个类由类加载器A来加载,那么这个类的依赖类也由该类加载A来进行加载。

这里的父子关系不是由继承实现的,而是由组合实现。

类加载顺序

  1. 判断被加载的类是否已经被加载过,如果是则结束,否则将加载任务委托给自己的父亲扩展类加载器(Ext ClassLoader);
  2. 父类加载器扩展类加载器(Ext ClassLoader)收到类加载请求后,也会先判断该类是否已经被加载过,如果是则结束,否则同样将加载任务委托给自己父类加载器根加载器(Bootstrap ClassLoader);
  3. 直到将加载任务委托给根加载器(Bootstrap ClassLoader),此时,Bootstrap ClassLoader会先判断该类是否已经被加载过,如果是则结束;
  4. Bootstrap ClassLoader会先判断能否完成加载任务,如果能则直接加载,否则会将加载任务交给儿子类加载器扩展类加载器(Ext ClassLoader);
  5. 儿子类加载器扩展类加载器(Ext ClassLoader)也会先判断能否完成加载任务,如果能则直接加载,否则会再次将加载任务交给儿子类加载器应用类加载器(Application ClassLoader);
  6. 不断的循环步骤5,直到最后的应用类加载器(Application ClassLoader)不能够加载这个类,就会抛出ClassNotFoundException异常。

    如何破坏双亲委派机制?

  7. Tomcat

假设有两个Web应用程序,都有一个User类,并且它们的类全限定名一样,例如都是:com.xxx.dto.User。
Tomcat给每个Web应用创建一个类加载器实例(WebAppClassLoader),该加载器重写了loadClass()方法,优先加载当前应用目录下的类,如果当前类加载器找不到,才会一层一层往上找。这样做到了Web应用层级的隔离。
所以说WebAppClassLoader打破双亲委派机制,实现Web应用之间依赖隔离。

Tomcat类加载器

并不是Web应用程序下的所有依赖都需要隔离,Redis就可以和Web应用程序之间共享(如果有需要),因为如果版本相同,没必要每个Web应用程序都独自加载一份。
Tomca在WebAppClassLoader上加了个SharedClassLoader,如果WebAppClassLoader自身没有加载到某个类的话,就会委托SharedClassLoader去加载。这样做是为了把应用程序之间需要共享的类放到一个共享目录下。为了隔离Web应用程序与Tomcat本身的类,又有类加载器(CatalinaCLassLoader)来加载Tomcat本身的依赖,如果Tomcat本身的依赖和Web应用程序还需要共享,那么还有类加载器(CommonClassLoader)来加载进而达到共享。各个类加载器的加载目录可以在Tomcat的Catalina.propertiees配置文件中看。
image.png

SharedClassLoader加载目录

image.png

CommonCLassLoader加载目录

image.png

  1. JDBC加载是否破坏了双亲委派机制?

以MySQL为例,JDBC定义了接口,具体实现类由具体厂商实现。
在使用JDBC的时候,使用DriverManager进而获取Connection,DriverManager在Java.sql包下,由Bootstrap CLassLoader类加载器加载;
当我们使用DriverManager.getConnection()时,得到的一定是厂商实现的类,但是Bootstrap ClassLoader不能加载到各个厂商实现的类,DriverManager解决方案是:在DriverManager初始化的时候,得到“线程上下文加载器”,去获取Connection的时候,使用“线程上下文加载器”去加载Connection,但是这里的“线程上下文加载器”实际上还是Application ClassLoader加载器,所以在获取Connection的时候,还是会先找Extensions ClassLoader和Bootstrap ClassLoader,只不过这两个加载器一定会加载不到,最终还是会由Application ClassLoader来加载。
这种情况下,有人会觉得破坏了双亲委派机制,因为本来应该由Bootstrap ClassLoader加载,改成了“线程上下文加载器”,改掉了类加载器;有人觉得没有破坏双亲委派机制,只是改成由“线程上下文加载器”进行类加载,但是还是遵守着“依次往上找父类加载器进行加载,都找不到时才由自身加载”。认为原则没有改变。
线程上下文加载器:由于类加载规则,很可能导致父加载器加载时依赖子加载器的类,导致无法加载成功(Bootstrap ClassLoader无法加载第三方类库),所以存在“线程上下文加载器”来进行加载。
image.png