目录
  1. 1. 双亲委派机制
    1. 1.1. 引导类加载器(c/c++)
    2. 1.2. 自定义类加载器(java)
      1. 1.2.1. 扩展类加载器
      2. 1.2.2. 系统(应用)类加载器
      3. 1.2.3. 自定义类加载器
    3. 1.3. 为什么要自定义加载器
    4. 1.4. 双亲委派机制
      1. 1.4.1. 反向委派机制
    5. 1.5. 双亲委派机制的好处
    6. 1.6. 补充
深入理解JVM--双亲委派机制

双亲委派机制

  • 在Java中,有两大类类加载器:引导类(启动类)加载器、自定义类加载器
  • 也可以分为四小类:引导类加载器、扩展类加载器、系统类加载器、自定义类加载器

引导类加载器(c/c++)

  • 底层是由 c、c++ 实现的,在java方法中是获取不到的,是 jvm 虚拟机的一部分
  • 主要用来加载核心类库 java、javax、sun 开头的包的
  • 因为他是由 c/c++ 编写的所以不需要继承 ClassLoader
  • 扩展类、系统类加载器都是由这个加载的

自定义类加载器(java)

扩展类加载器

  • 间接继承与ClassLoader
  • 父类加载器为引导类加载器(非继承关系、类似于上下级关系)
  • 主要加载目录为 jre/lib/ext 文件夹下的 jar包
  • 如果用户自己写的 jar 包扔到那也会被加载

系统(应用)类加载器

  • 间接继承与ClassLoader
  • 父类加载器为扩展类加载器
  • 主要加载为当前用户写的类 classpath 路径下的类
  • 即我们自定义的 java 类都是由系统类加载器加载的

自定义类加载器

  • 这个就是我们继承 ClassLoader 类,重写他的 loadClass 方法或者 findClass 方法所实现的加载器
  • 我们可以复杂的实现,自己处理二进制字节码流,自定义加密解密了
  • 或者简单实现直接继承 URLClassLoader 不用处理字节码流

为什么要自定义加载器

  1. 隔离加载类:框架中用的比较多,以避免类路径一直而冲突
  2. 修改类加载方式
  3. 扩展加载源
  4. 防止源码泄漏:加密解密

双亲委派机制

image-20200211200530592

机制原理:

​ 一个类加载器收到了类加载的请求时,他并不会直接先去自己加载,而是委托给父类加载器来执行,如果父类加载器还有父类则递归直到顶层加载器。如果已经加载则不用加载,若加载未成功,这时才会自己尝试去加载,我们看一下 loadClass 源码更容易理解这一过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//首先查看该类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果父级不为空则交给父级去处理
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果父级为空那么证明父级就是引导类加载器了,交给他去处理
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {

}

if (c == null) {
//如果还找不到则自己去尝试加载
long t1 = System.nanoTime();
c = findClass(name);

sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

反向委派机制

​ 这里也是个小知识点,在我们调用第三方方法时,我们的核心类库是没有这些类的,比如 jdbc,我们的引导类加载器会去加载这个接口,但是这个接口的实现类,就会通过反向委派的机制交给线程上下文类加载器,(本质上就是系统类加载器),让他去加载这个接口的实现类

image-20200211203647264

双亲委派机制的好处

假如我们自己写个 String 类,包名都起的一样:

image-20200211202759590

这样的话执行 main 方法会怎么样?

image-20200211202824730

肯定会报错,因为双亲委派的机制,我们的 String 属于核心 API 所以会在引导类加载器处直接加载了,那么 String 类里怎么可能有 main 方法呢,所以会出现错误

那么好处就显而易见了。

  • 避免类被重复加载
  • 保护程序安全,防止核心 API 被篡改(也成为沙箱安全机制)

补充

如何判断两个 Class 对象是一样的

  • 类的完整类名必须一致,包括包名
  • 加载这个类的 ClassLoader 类,必须相同

jvm 必须知道一个类型是由引导类加载器加载的还是用户类加载器加载的

如果一个类是由用户类加载器加载的,那么 jvm 会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。

类的主动使用和被动使用:

  • 不会导致类的初始化,叫做的类的被动使用
文章作者: Archiver
文章链接: https://www.kaiming66.com/2019/12/10/jvm/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3JVM--%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Archiver`s Blog
打赏
  • 微信
  • 支付寶

评论