JVM 类加载机制以及双亲委派机制 分析总结

x33g5p2x  于2021-09-18 转载在 Java  
字(2.7k)|赞(0)|评价(0)|浏览(404)

杂谈

项目终于上线,可以有空闲时间继续写博客了。时隔一年,从新看 java 的一些基础知识,发现对于类加载和双亲委派这方面的理解,更明白了一些(具体原因是以前源代码看不下去…),还是整理一下好了。
ps(本文主要分析一下双亲委派机制

类加载机制

首先大家都理解,运行java程序,底层是由 C++ 实现的,那么 java 执行命令运行代码壶进行以下几个步骤:

  • C++ 会创建一个引导类加载器实例
  • java 创建 JVM 启动器,由 引导类加载器 加载其它加载器
  • 运行类在加载器中进行寻找,若有就返回,若没有就进行加载
  • 加载完成后 JVM 会执行main方法入口,由C++进行调用

类加载有以下几个步骤

  1. 加载:加载使用到的类,例如 A 类,当使用的时候去类加载器里面寻找
  2. 验证:校验字节码对象是否正确
  3. 准备:给类中的静态变量进行内存的分配,并且进行赋值
  4. 解析:又称为静态链接过程,把一些符号引用替换成直接引用
  5. 初始化:执行静态代码块

双亲委派机制

要想了解 双亲委派机制 还是要谈谈上面提到的类加载器。

类加载器

java 有以下几种类加载器:

  • 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库。
  • 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包。
  • 应用程序类加载器:负责加载ClassPath路径下的类包,就是我们自己写的类。
  • 自定义加载器:负责加载用户自定义路径下的类包,例如Tomcate就实现自定义加载器打破了双亲委派机制。

类加载器的初始化过程

类加载器的初始化过程也是这次我才去看源代码,才发现,哦~是这个样子,不然之前书上纯理论还是有点太片面,跟一下代码还是有必要的。

首先 C++ 会调用到 sun.misc.Launcher.getLauncher() 进行初始化

在这里插入图片描述

很明显可以看到,这本质上是一个单例模式,那么接下来我们观察一下它的构造方法。

在这里插入图片描述

构造方法核心是前面的初始化加载器。

加载类扩展加载器

首先我们先关注一下第一个 getExtClassLoader() 方法,这个方法是加载扩展类加载器

在这里插入图片描述

createExtClassLoader() 用于加载扩展类加载器以及校验,我们跟进去看。

在这里插入图片描述

new Launcher.ExtClassLoader 这就是真正加载的地方。

在这里插入图片描述

第一个参数是文件路径,在 getExtClassLoader() 中,我们找到 getExtDirs() 方法进行查看,看看到底传了什么路径。

在这里插入图片描述

在这里插入图片描述

由此我们可以知晓 扩展类加载器加载了 JRE的lib目录下的ext扩展目录中的JAR类包

第二个参数是用于双亲委派机制的父节点,这点留着稍后讲解,先将代码附上。

在这里插入图片描述

最终会调用到父类 ClassLoader

在这里插入图片描述

在这里插入图片描述

至于为什么在进行加载扩展加载器的时候会转null呢,因为这边的父类是 引导类加载器,而引导类加载器又是由C++生成的,所以这边传null。

第三个参数是当创建Url的时候使用,初始化在Launcher实现。
在这里插入图片描述

加载应用程序类加载器

接下来我们来看第二个方法:

Launcher.AppClassLoader.getAppClassLoader()

但是这边我们注意,它将实例好的扩展类加载器传入了本身,我们来看看它做了什么

在这里插入图片描述

在这里插入图片描述

首先这边需要进行以下描述,这边的 super调用的方法和 类加扩展加载器 调用的super一致,同样是调用

在这里插入图片描述

此方法。

我们继续分析一下三个传参。

第一个传参依旧是路径,此路径加载了我们平常代码自定义的类,看看,多熟悉的 java.class.path

第二个传参将上一步生成的 类扩展加载器 传入,作为此加载器的父节点,用于接下来的双亲委派机制的实现

第三个传参与上雷同。

双亲委派机制代码详解

先上网友的图 ps(图画的真好)

在这里插入图片描述

抛开自定义加载器不谈,我们先来一波理论知识讲一下双亲委派机制。

首先我们自己写了一个A类,此类在进行加载的时候,会先进入到应用程序加载类查看加载了没有,如果没有,会把这个类向上委托,看看扩展类加载器加载了没有,要是还没有,就继续向上委托,最终看看引导类加载器加载了没有,如果引导类加载器也没有加载,那么就在自己的加载类路径下看看有没有这个类,有就进行加载,没有就让下一个节点扩展类加载器进行加载,最终会让应用程序类加载器进行加载(如果没有自定义加载器的话),要是还是找不到,就会抛出ClassNotFound异常。

其实可以用一句话总结:先找父节点加载,不行再由子节点加载。

注意点:这边说的都是父节点子节点,并不是类,不是继承关系!!!!

接下来看看源码。

当加载的时候,最后都会运行到ClassLoader的loadClass方法

在这里插入图片描述

此方法是实现双亲委派机制的核心代码,主要逻辑有三点。

第一点:findLoadedClass()

查看此类是否被当前加载器加载,若果是,则返回,如果不是,就找寻父节点进行判断是否被加载过。
ps(此处逻辑简单清晰,不推荐深究下面的源代码,下面由C++实现,深究有风险(崩溃…)

第二点:当此加载器并没有加载这个类的时候,找寻父节点进行判断。

首先,你得有父节点,所以此处的父节点就是前面所讲 类加载扩展器应用程序类加载器的****第二个传参的对象。只有一种情况父节点会是 null 那就是此处的加载器是 类加载扩展器,这时候就会调用 findBootstrapClassOrNull() 进行寻找。

第三点:如果所有的类加载器都没有加载过,那么就需要判断当前加载类的加载路径中有没有这个类。

由于第二点判断存在与否,若不存在会寻找父节点进行判断,所以若最终进入到此步骤,就只能说明所有的类加载器都没有加载过,此时会从 顶 节点加载器开始判断本加载器路径是否存在此类。我们跟寻findClass进行查看

在这里插入图片描述

若最终查询不到,将会抛出异常,ClassNotFoundException。

ps(此处逻辑代码也非常清晰,不推荐继续往下抛,下面就是加载的详细过程

补充

双亲委派机制其实逻辑很简单,但是书上的理论和实际刨代码的理解毕竟还是有点不同。

为什么要设计双亲委派机制?

  • 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性。
  • 由于 90% 以上的类都是我们自己创造的,所以由子节点往父节点寻找,能使得加载一次过后,绝大部分的类都能在第一个(自定义/应用程序类加载器)加载器中找到。
  • 沙箱安全:上层的类不会被随意修改,即便自己写了一个路径一样类名也一样的类,也不会影响到核心库的api。

为什么有人打破双亲委派?

比如 Tomcat 就是打破了双亲委派,因为它是一个web容器,需要部署多个运用程序,为了避免不同程序依赖版本不一样的第三方库导致的问题,所以需要打破它。

相关文章