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

类加载机制以及双亲委派机制

    • 杂谈
    • 类加载机制
      • 类加载有以下几个步骤
    • 双亲委派机制
      • 类加载器
      • 类加载器的初始化过程
        • 加载类扩展加载器
        • 加载应用程序类加载器
      • 双亲委派机制代码详解
    • 补充

杂谈

项目终于上线,可以有空闲时间继续写博客了。时隔一年,从新看 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() 进行初始化
JVM 类加载机制以及双亲委派机制 分析总结_第1张图片
很明显可以看到,这本质上是一个单例模式,那么接下来我们观察一下它的构造方法。

JVM 类加载机制以及双亲委派机制 分析总结_第2张图片
构造方法核心是前面的初始化加载器。

加载类扩展加载器

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

JVM 类加载机制以及双亲委派机制 分析总结_第3张图片

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

JVM 类加载机制以及双亲委派机制 分析总结_第4张图片

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

在这里插入图片描述

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

JVM 类加载机制以及双亲委派机制 分析总结_第5张图片

JVM 类加载机制以及双亲委派机制 分析总结_第6张图片
由此我们可以知晓 扩展类加载器加载了 JRE的lib目录下的ext扩展目录中的JAR类包

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

JVM 类加载机制以及双亲委派机制 分析总结_第7张图片
最终会调用到父类 ClassLoader
JVM 类加载机制以及双亲委派机制 分析总结_第8张图片
在这里插入图片描述
至于为什么在进行加载扩展加载器的时候会转null呢,因为这边的父类是 引导类加载器,而引导类加载器又是由C++生成的,所以这边传null。

第三个参数是当创建Url的时候使用,初始化在Launcher实现。JVM 类加载机制以及双亲委派机制 分析总结_第9张图片

加载应用程序类加载器

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

Launcher.AppClassLoader.getAppClassLoader()

但是这边我们注意,它将实例好的扩展类加载器传入了本身,我们来看看它做了什么
JVM 类加载机制以及双亲委派机制 分析总结_第10张图片

JVM 类加载机制以及双亲委派机制 分析总结_第11张图片
首先这边需要进行以下描述,这边的 super调用的方法和 类加扩展加载器 调用的super一致,同样是调用
JVM 类加载机制以及双亲委派机制 分析总结_第12张图片
此方法。

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

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

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

第三个传参与上雷同。

双亲委派机制代码详解

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

JVM 类加载机制以及双亲委派机制 分析总结_第13张图片
抛开自定义加载器不谈,我们先来一波理论知识讲一下双亲委派机制。

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

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

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

接下来看看源码。

当加载的时候,最后都会运行到ClassLoader的loadClass方法
JVM 类加载机制以及双亲委派机制 分析总结_第14张图片
此方法是实现双亲委派机制的核心代码,主要逻辑有三点。

第一点:findLoadedClass()

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

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

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

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

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

JVM 类加载机制以及双亲委派机制 分析总结_第15张图片
若最终查询不到,将会抛出异常,ClassNotFoundException。

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

补充

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

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

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

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

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

你可能感兴趣的:(java,java,jvm)