2.双亲委派机制详细解析及原理

写在前面的话:为什么要研究类加载的过程?为什么要研究双亲委派机制?

研究类加载的过程就是要知道类加载的时候使用了双亲委派机制。但仅仅知道双亲委派机制不是目的,目的是要了解为什么要使用双亲委派机制,他的原理是什么?知道双亲委派机制的逻辑思想,然后这个思想是否可以被我们借鉴,为我所用。这才是学习知识的目的。

比如:双亲委派机制,避免了类的重复加载,避免了核心类库被修改。那么,我们在做框架设计的时候,框架底层的东西是不是应该是不容被串改的,或者不可以被黑客进攻的,那么我们就可以借鉴双亲委派机制了。

再比如:双亲委派机制的实现使用了责任链设计模式,我们借此可以研究一下责任链设计模式,这样就理解了委派的原理。那么哪些场景我们可以使用责任链设计模式呢?多思考,才是学习的目的和精髓所在。学到的东西,能用在工作中,才是王道。

一、什么是双亲委派机制

我们先来看一个案例: 打印引导类加载器, 扩展类加载器, 应用程序类加载器加载的目录

package com.lxl.jvm;
import sun.misc.Launcher;
import java.net.URL;

public class TestJDKClassLoader {
    public static void main(String[] args) {
        System.out.println();
        System.out.println("bootstrap Loader加载以下文件:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i<urls.length; i++) {
            System.out.println(urls[i]);
        }

        System.out.println();
        System.out.println("extClassLoader加载以下文件");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        System.out.println("appClassLoader加载以下文件");
        System.out.println(System.getProperty("java.class.path"));
    }
}

我们来看一下:

引导类加载器加载的文件是:Launcher.getBootstrapClassPath().getURLs()下的文件

扩展类加载器加载的文件是: java.ext.dirs , java扩展类目录

应用程序类加载器, 加载的是: java.class.path , java home路径下的所有类

我们来看一下打印结果

bootstrap Loader加载以下文件:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/classes

extClassLoader加载以下文件
/Users/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

appClassLoader加载以下文件
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/deploy.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/dnsns.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/jaccess.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/localedata.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/nashorn.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunec.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/zipfs.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/javaws.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jfxswt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/management-agent.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/plugin.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/ant-javafx.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/dt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/javafx-mx.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/jconsole.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/packager.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/sa-jdi.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/tools.jar:
/Users/Downloads/workspace/project-all/target/classes:
/Users/responsitory/org/springframework/boot/spring-boot-starter/2.2.8.RELEASE/spring-boot-starter-2.2.8.RELEASE.jar:
/Users/responsitory/org/springframework/boot/spring-boot/2.2.8.RELEASE/spring-boot-2.2.8.RELEASE.jar:
/Users/responsitory/org/springframework/spring-context/5.2.7.RELEASE/spring-context-5.2.7.RELEASE.jar:
/Users/responsitory/org/springframework/spring-aop/5.2.7.RELEASE/spring-aop-5.2.7.RELEASE.jar:
/Users/responsitory/org/springframework/spring-beans/5.2.7.RELEASE/spring-beans-5.2.7.RELEASE.jar:
/Users/responsitory/org/springframework/spring-expression/5.2.7.RELEASE/spring-expression-5.2.7.RELEASE.jar:
/Users/responsitory/org/springframework/boot/spring-boot-autoconfigure/2.2.8.RELEASE/spring-boot-autoconfigure-2.2.8.RELEASE.jar:
/Users/responsitory/org/springframework/boot/spring-boot-starter-logging/2.2.8.RELEASE/spring-boot-starter-logging-2.2.8.RELEASE.jar:
/Users/responsitory/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:
/Users/responsitory/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:
/Users/responsitory/org/apache/logging/log4j/log4j-to-slf4j/2.12.1/log4j-to-slf4j-2.12.1.jar:
/Users/responsitory/org/apache/logging/log4j/log4j-api/2.12.1/log4j-api-2.12.1.jar:
/Users/responsitory/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:
/Users/responsitory/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:
/Users/responsitory/org/springframework/spring-core/5.2.7.RELEASE/spring-core-5.2.7.RELEASE.jar:
/Users/responsitory/org/springframework/spring-jcl/5.2.7.RELEASE/spring-jcl-5.2.7.RELEASE.jar:
/Users/responsitory/org/yaml/snakeyaml/1.25/snakeyaml-1.25.jar:
/Users/responsitory/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar:

/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar

通过观察,我们发现

引导类加载器,确实只加载了java home下的/jre/lib目录下的类

扩展类加载器加载了java扩展目录里面的类

但是, 应用程序类加载器, 加载的类包含了java home下/jre/lib目录, java home扩展目录下的类, 还有responsitory仓库下的类, 还有idea的类, 还有就是我们的类路径下target的类.

问题来了, 为什么AppClassLoader加载器加载了引导类加载器和扩展类加载器要加载的类呢? 这样加载不是重复了么?

其实, 不会重复加载, appClassLoader主要加载的类就是target目录下的类, 其他目录下的类事实上基本不会加载. 为什么呢? 这是因为双亲委派机制.

1187916-20200628175926791-549460214.png

上面这个图就是双亲委派机制的图. 这也是类加载的原理。一共分为两部分:

  • 一部分是查找
  • 另一部分是加载

就以自定义的java.lxl.jvm.Math类为例,我们来看看这个类是如何被类加载器加载的。

第一步: 首先是由应用程序类加载器去查找java.lxl.jvm.Math类, 他要去看他已经加载的类中是否有这个类, 如果有, 就直接返回, 如果没有, 就去加载这个类,但是不是由应用程序类加载器直接加载。而是委托他的父类也就是扩展类加载器去加载。

第二步:扩展类加载器也是先搜索,查看已经加载的类是否有java.lxl.jvm.Math, 如果有就返回,如果没有就加载这个类。在加载的时候,也不是由自己来加载,而是委托他的父类,引导类加载器去加载。

第三步:引导类加载器先查找已经加载的类中是否有这个类,有则返回,没有就去加载这个类。这时候, 我们都知道, Math类是我自己定义的, 引导类加载器中不可能有, 加载失败,所以, 他就会去加载这个类。回去扫描/lib/jar包中有没有这个类,发现没有,于是让扩展类加载器去加载, 扩展类加载器会去扫描扩展包lib/jar/ext包,里面有没有呢? 当然也没有, 于是委托应用程序类加载器, ok, 应用程序类加载器是有的, 于是就可以加载了, 然后返回这个类。

【通过分析,我们可以得出,双亲委派机制的实现使用的是责任链设计模式。】

那么, 这里有一个问题, 那就是, 由应用程序类加载器首先加载, 然后最后又回到了应用程序类加载器. 绕了一圈又回来了, 这样是不是有些多此一举呢, 循环了两次? 为什么一定要从应用程序类加载器加载呢? 直接从引导类加载器加载不好么?只循环一次啊....

其实, 对于我们的项目来说, 95%的类都是我们自己写的, 因此, 而我们自己写的类是有应用程序类加载器加载. 其实,应用程序类加载器只有在第一次的时候, 才会加载两次. 以后, 当再次使用到这个类的时候, 直接去问应用程序类加载器, 有这个类么? 已经有了, 就直接返回了.

二、 源码分析双亲委派机制

1187916-20200627191708469-1781924332.png

还是从这张图说起,c++语言调用了sun.misc.Launcher.getLauncher()获取了launcher对象,Launcher类初始化的时候其构造器创建了ExtClassLoader和AppClassLoader。然后接下来调用launcher对象的getClassLoader()方法。

public ClassLoader getClassLoader() {
    return this.loader;
}

getClassLoader()返回了this.loader对象。 而loader对象是在Launcher初始化的时候进行了赋值, loadClass是AppClassLoader。

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
          	// loader的值是AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
  ......
}

类加载器是如何加载类的呢?

调用了loader.loadClass("com.lxl.Math")方法.我们来看一下类加载器.类加载器主要调用的是classLoader.loadClass("com.lxl.Math") 这个方法来实现双亲委派机制的. 根据上面的分析, 我们知道, 在Launcher类初始化的时候, loadClass是AppClassLoader, 那么也就是说, 双亲委派机制的起点是AppClassLoader.

下面我们来看一下源码, 我们采用断点的方式来分析 。

2.1第一次向上查找

1. 从AppClassLoader加载目标类

首先, 我们在Launcher的AppClassLoader的loadClass(String var1, boolean var2) 这个方法添加一个断点, 并将其赋值为我们的com.lxl.jvm.Math类

1187916-20200628204745008-1901923919.png

然后运行Math的main方法,我们来看一下这个类到底是如何被加载的

1187916-20200628204851183-709654203.png

启动debug调试模式, 首先进入了Launch.AppClassLoader.loadClass(....)方法

1187916-20200628205201483-600260324.png

我们来具体看看这个方法的实现

1187916-20200628205428734-765133067.png

上面都是在做权限校验, 我们看重点代码.

Launcher.AppClassLoader.loadClass(...)
  
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
  int var3 = var1.lastIndexOf(46);
  if (var3 != -1) {
    SecurityManager var4 = System.getSecurityManager();
    if (var4 != null) {
      var4.checkPackageAccess(var1.substring(0, var3));
    }
  }

  // 缓存中是否有目标路径,如果有,说明之前已经加载过,直接调动findLoadedClass()从已经加载的类中查找,找到后直接返回。
  if (this.ucp.knownToNotExist(var1)) {
    Class var5 = this.findLoadedClass(var1);
    if (var5 != null) {
      if (var2) {
        this.resolveClass(var5);
      }

      return var5;
    } else {
      throw new ClassNotFoundException(var1);
    }
  } else {
    // 缓存中没有,则调用loadClass加载类。
    return super.loadClass(var1, var2);
  }
}

看注释部分,我们知道这是双亲委派机制里的第一步,现在AppClassLoader中查找,先从已经加载过的类中查找,如果找到就直接返回, 如果没找到,则加载这个类。我们分两步来看:一部分是findLoaderClass()的源码, 另一部分是super.loadClass(...)的源码

第一步:在已加载的类中查找是否存在

if (this.ucp.knownToNotExist(var1)) {
    Class var5 = this.findLoadedClass(var1);
    if (var5 != null) {
      if (var2) {
        this.resolveClass(var5);
      }

      return var5;
    } else {
      throw new ClassNotFoundException(var1);
    }
  }

调用findLoaderClass(var1)之前先判断this.ucp.knownToNotExist(var1)在缓存中是否存在,如果存在则调用this.findLoadedClass(var1);查找。而findLoadedClass最终调用的是本地方法查找

private native final Class<?> findLoadedClass0(String name);

第二步:之前没有加载过此类,首次加载

else {
    // 缓存中没有,则调用loadClass加载类。
    return super.loadClass(var1, var2);
  }

首次加载调用了super.loadClass(var1,var2), 而这个super是谁呢? 我们来看看AppClassLoader的集成关系

在mac上按option+command+u查看集成关系图

1187916-20200628210125290-1853047411.png

我们看到AppClassLoader继承自URLClassLoader, 而URLClassLoader又继承了上面四个类,最终有继承一个叫做ClassLoader的类, 所有的类加载器, 最终都要继承这个ClassLoader类.

而这里调用的是super.loadClass(),我们来看看URLClassLoader中是否有loadClass()类, 看过之后发现,他没有, 最终这个super.loadClass()是继承了ClassLoader类的loadClass(....)方法

1187916-20200628210534475-1695274979.png

正是这个类实现了双亲委派机制, 下面我们就来看看, 他到底是怎么实现的?

当前的类加载器是AppClassLoader类加载器, 首先第一步是查找AppClassLoader中已经加载的类中,有没有这个类, 我们看到这里有检查了一遍。

1187916-20200629101543007-1895103811.png

通过调用findLoadedClass(name)方法来查询已经加载的类中, 有没有com.lxl.jvm.Math类. 那么findLoadedClass(name)里面做了什么呢? 我们进去看看

1187916-20200629101815289-1783213335.png

我们看到, findLoaderClass(name)方法调用了自己的一个方法findLoadedClass0, 这个方法是native的, 也就是是本地方法, 使用c++实现的, 我们不能看到底部的具体实现细节了. 但是大致的逻辑就是在已经加载的类中查找有没有com.lxl.jvm.Math这个类, 如果有就返回Class类信息.

1187916-20200629104311203-361523177.png

debug看到,显然是没有的, 接下来就是走到if(c == null)里面了, 这里做了什么事呢?

1187916-20200629110359840-246062311.png

他判断了,当前这个类加载器的parent是否是null. 我们知道当前这个类加载是 AppClassLoader, 他的parent是ExtClassLoader, 自然不是null, 所以, 就会执行里面的parent.loadClass(name, false);

2. 从ExtClassLoader中加载目标类

也就是执行扩展类加载器的loadClass(...)方法. 我们来看看扩展类 ExtClassLoader

1187916-20200629110720267-871386425.png

我们发现ExtClassLoader类里面没有loadClass(...)方法, 那他没有, 肯定就是在父类里定义的了, 通过查找, 最后我们发现这个方法还是ClassLoader里的loadClass(...)方法. 于是,我们继续debug.肯定会再次走到loadClass(...)这个方法里来. 而此时, loadClass是ExtClassloader的loadClass(...)方法

1187916-20200629111141915-750788362.png

果然, 又走到这个方法里面来了

继续往下执行, 首先查找ExtClassLoader中已经加载的类中,是否有java.lxl.jvm.Math类, 过程和上面是一样的. 最后调用的是本地方法.

我们知道, 这肯定是没有的了. 然后继续判断, ExtClassLoader的parent是否为空. 很显然, 他就是空啊, 因为ExtClassLoader的父类加载器是引导类加载器BootStrapClassLoader, 而引导类加载器是c++写的,所以,这里的parent为空. parent为空执行的是else中的代码

3.从BootStrapClassLoader中查找

1187916-20200629111356315-1893717796.png

这个方法就是去引导类加载器BootstrapClassLoad中查找, 是否有这个类, 我们来看看引导类加载器里面的具体实现

1187916-20200629111632410-259877826.png

我们发现, 最后具体的逻辑也是一个本地方法实现的. 我们还是猜测一下, 这就是去查找引导类加载器已经加载的类中有没有com.lxl.jvm.Math, 如果有就返回这个类, 如果没有就返回null.

很显然, 是没有的. c == null. 我们继续来看下面的代码

到此为止, 我们第一次向上查找的过程就完完事了. 用图表示就是这样

1187916-20200629112126168-1409795832.png

首先有应用程序类加载器加载类, 判断应用程序已加载的类中, 是否有这个类, 结果是没有, 没有则调用其父类加载器ExtClassLoader的loadClass()方法, 去扩展类加载器中查找是否有这个类, 也没有. 那么判断其父类是否为空, 确实为空, 则进入到引导类加载器中取查找是否有这个类, 最后引导类加载器中也没有, 返回null

2.2 类加载器向下委派加载

下面来看看类加载器是如何向下委派的?

1.启动类加载器加载目标类

引导类加载器中也没有这个类, 返回null, 这里的返回空包含了两个步骤,一个是查找,没找到,二是没找到后去/lib/jar目录下加载这个类,也没有加载到。最后返回null。然后回到ExtClassLoader.loadClass(...).

2.扩展类加载器加载目标类

接下来调用findClass(name);查找ExtClassLoader中是否有com.lxl.jvm.Math, 我们来看看具体的实现. 首先这是谁的方法呢?是ExtClassLoader的.

1187916-20200629111923696-1919134506.png

进入到findClass(name)方法中, 首先看看ExtClassLoader类中是否有这个方法, 没有, 这里调用的是父类UrlClassLoader中的findClass()方法

1187916-20200629121201248-1232034000.png

在findClass()里面, 我们看到将路径中的.替换为/,并在后面增加了.class. 这是在干什么呢? 是将com.lxl.jvm.Math替换为com/lxl/jvm/Math.class,这就是类路径

然后去resource库中查找是否有这个路径. 没有就返回null, 有就进入到defineClass()方法.

我们想一想, 在ExtClassLoader类路径里面能找到这个类么?显然是找不到的, 因为这个类使我们自己定义的.

他们他一定执行return null.

1187916-20200629132256509-2078454907.png

正如我们分析, debug到了return null; 这时执行的ExtClassLoader的findClass(). 返回null, 回到AppClassLoader加载类里面

3.应用程序类加载器加载目标类

1187916-20200629132619742-263584260.png

c就是null, 然后继续执行findClass(name), 这时还是进入到了URLClassPath类的findClass(name)

1187916-20200629132751688-722596722.png

如上图, 此时调用的是AppClassLoader的findClass(name), 此时的resource还是空么?当然不是了, 在target目录中就有Math.class类, 找到了, 接下来执行defineClass(name,res)

defindClass这个方法是干什么的呢? 这个方法就是加载类. 类已经找到了, 接下来要做的就是将其加载进来了.

类加载的四个步骤

defindClass()这个类执行的就是类加载的过程。 也就是下图中的四个步骤:验证->准备->解析->初始化。如下图红线圈出的部分.

1187916-20200629133141253-1343704622.png

再看看这四个步骤:

private Class<?> defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    // 获取classes目录的绝对路径,如:file:/Users/用户名/workspace/demo/target/classes/
    URL url = res.getCodeSourceURL();
    if (i != -1) {
      	// 获取包名
        String pkgname = name.substring(0, i);
        // Check if package already loaded.
        Manifest man = res.getManifest();
        definePackageInternal(pkgname, man, url);
    }
    // Now read the class bytes and define the class
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        // Use (direct) ByteBuffer:
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, bb, cs);
    } else {
        byte[] b = res.getBytes();
        // must read certificates AFTER reading bytes.
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, b, 0, b.length, cs);
    }
}

这里面的核心逻辑代码都是本地方法。我们能看到的通常是一些基础的校验,比如准备阶段,解析阶段,初始化阶段都是本地方法

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
  			// 预定义类信息
        protectionDomain = preDefineClass(name, protectionDomain);
  			// 定义类源码
        String source = defineClassSourceLocation(protectionDomain);
  			// 初始化类
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        // 类定义后置处理
        postDefineClass(c, protectionDomain);
        return c;
    }

这块代码了解即可。不用深入研究。

以上就是双亲委派机制的源码.

那么当下一次在遇到com.lxl.jvm.Math类的时候, 我们在AppClassLoader中就已经有了, 直接就返回了.

在来看一遍双亲委派机制的流程图

1187916-20200629133430210-1410184450.png

三、为什么要有双亲委派机制?

两个原因: 
1. 沙箱安全机制, 自己写的java.lang.String.class类不会被加载, 这样便可以防止核心API库被随意修改
2. 避免类重复加载. 比如之前说的, 在AppClassLoader里面有java/jre/lib包下的类, 他会加载么? 不会, 他会让上面的类加载器加载, 当上面的类加载器加载以后, 就直接返回了, 避免了重复加载.

我们来看下面的案例

加入, 我在本地定义了一个String类, 包名是java.lang.String. 也就是是rt.jar包下的String类的包名是一样的哈.

1187916-20200629140722943-345510080.png

如上图, 这是我们运行main方法, 会怎么样? 没错, 会报错

1187916-20200629140756668-714683873.png

下面分析一下, 为什么会报错呢?

还是看双亲委派机制的流程, 首先由AppClassLoader类加载器加载, 看看已经加载的类中有没有java.lang.String这个类, 我们发现, 没有, 找ExtClassLoader加载, 也没有, 然后交给引导类BootStrapClassLoader加载, 结果能不能找到呢? 当然可以了. 但是这个java.lang.String是rt.jar中的类, 不是我们自定义的类, 加载了rt.jar中的java.lang.String类以后, 去找main 方法, 没找到.....结果就抛出了找不到main方法异常.

所以说, 如果我们自己定义的时候, 想要重新定义一个系统加载的类, 比如String.class, 可能么? 不可能, 因为自己定义的类根本不会被加载

这就是双亲委派机制的第一个作用: 沙箱安全机制, 自己写的java.lang.String.class类不会被加载, 这样便可以防止核心API库被随意修改

双亲委派机制还有一个好处: 避免类重复加载. 比如之前说的, 在AppClassLoader里面有java/jre/lib包下的类, 他会加载么? 不会, 他会让上面的类加载器加载, 当上面的类加载器加载以后, 就直接返回了, 避免了重复加载.

第三个作用:全盘委托机制。比如Math类,里面有定义了private User user;那么user也会由AppClassLoader来加载。除非手动指定使用其他类加载器加载。也就是说,类里面调用的其他的类都会委托当前的类加载器加载。



分类: JVM虚拟机
0
0
«  上一篇: 1. JVM核心类加载器及类加载的全过程
»  下一篇: 3.代码实现自定义类加载器
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值