跳至主要內容

JVM (三) 从 JVM 源码角度看类加载器层级关系和双亲委派

安图新大约 4 分钟

JVM (三) 从 JVM 源码角度看类加载器层级关系和双亲委派

类加载器我们都知道有如下的继承结构,这个关系只是逻辑上的父子关系。

我们一直听说引导类加载器没有实体,为什么没有实体呢?

因为引导类加载器只是一段 C++代码并不是什么实体类,所谓的引导类加载器就是那一段 C++逻辑,让 JVM 从 C++代码转到 Java 代码来加载其它类。

 
 

一:引导类加载器/启动类加载器到底是啥?

JVM 启动后要找我们的 Main Class,在 openJDK 中有一个 java.c 的 C++文件

 
 

在这个文件中我们可以看到 JVM 是怎么加载 mainClass 的。

 
 
 
 

在第二步中,会调用 cls 的 checkAndLoadMain 方法,那这个 cls 是什么东西?GetLauncherHelperClass 返回是个什么? 我接着看。

 
 

关键点来了。FindBootStrapClass 通过动态链接库加载需要的类。加载到 sun/launcher/LauncherHelper 类。

 
 

启动类加载器就是上面这个逻辑,主要目的就是找到 sun/launcher/LauncherHelper 这个类。找到这个类之后再去创建扩展类加载器,应用类加载器。

我们回到上面的第二步,知道 cls 就是 LauncherHelper 了。看下这个类里面的 checkAndLoadMain 方法,这个类在 rt.jar 包里面。

 
 

a 处我们的看到通过 ClassLoader.getSystemClassLoader()得到一个类加载器。在 c 处通过这个类加载器加载 MainClass。

这个 MainClass 怎么得到的可以从 b 处看到,我们如果自己需要获取 jar 的相关信息可以参考里面的逻辑方法,具体内容不再探讨。

那么我们就去看下 a 处的这个类加载器是那个?

 
 
 
 

sc1 是由 Launcher.getLauncher()中的 getClassLoader()得到的。

 
 

我们看 this.loader 是怎么赋值的。

 
 

终于看到们熟悉的东西了!!! 原来扩展类加载器 ExtClassLoader 和应用类加载器 AppClassLoader 都是 Launcher 的内部类。

二:扩展类加载器 ExtClassLoader 创建

 

 
 

首先我们看到扩展类加载器继承 URLClassLoader。其加载的路径是系统变量java.ext.dirs。 在第三步构造函数里面我们看到创建扩展类加载器的时候第二个参数传入的就是 null,这个参数就是父类构造器。

 

这下我们知道为啥在项目中获取扩展类加载器父加载器的时候得到的为空了。

三:系统类加载器创建

从上面得到 AppClassLoader 的时候把扩展类加载器作为参数传了进去。

 
 

系统类加载器加载的路径是系统参数为:java.class.path 路径。创建它的时候把扩展类加载器作为父类传了进去。

上面把得到的系统类加载器设置为了线程上下文的类加载器。这一点很重要。

到此我们从代码的角度看到引导类加载器,扩展类加载器,系统类加载器是怎么维护他们之间的逻辑关系的。他们的这种关系也是双亲委派的基础也是打破双亲委派的切入点。

关于类加载器加载的路径,可以通过下面的代码输出看到。

  View Code

四:双亲委派

当使用一个类加载器进行 classLoader.loadClass(String name)的时候都会走到下面的这个逻辑。(记住这个方法是 protected 的,前提是自定义类加载器不重写这个方法)

 
 

1:检查需要加载的 class 是否已经加载了,如果加载过了直接返回。

2:如果没有加载过,先看父加载器是否为空,(这个 parent 参数在创建类加载器的时候当做参数传进来的,上面内容已经说过。),不为空的话让父类加载器尝试加载。父类加载器还有父类加载器就传递上去加载。

3:直到父类加载器为空,即到扩展类加载器,parent 为空,就调用 native 方法通过动态链接通过 C++代码来加载,还记得上面 FindBootStrapClass 吗 ,就是它来加载的。

4:当所有父类加载不到的时候就走到 findClass(name)了,这也是个 protected 的,需要自定义类加载来实现加载方法。也就是所说的父类加载不到才由自己加载。

下一节记录怎么打破双亲机制。

上次编辑于:
贡献者: Andy