2.雙親委派機制詳細解析及原理


寫在前面的話:為什么要研究類加載的過程?為什么要研究雙親委派機制?

研究類加載的過程就是要知道類加載的時候使用了雙親委派機制。但僅僅知道雙親委派機制不是目的,目的是要了解為什么要使用雙親委派機制,他的原理是什么?知道雙親委派機制的邏輯思想,然后這個思想是否可以被我們借鑒,為我所用。這才是學習知識的目的。

比如:雙親委派機制,避免了類的重復加載,避免了核心類庫被修改。那么,我們在做框架設計的時候,框架底層的東西是不是應該是不容被串改的,或者不可以被黑客進攻的,那么我們就可以借鑒雙親委派機制了。

再比如:雙親委派機制的實現使用了責任鏈設計模式,我們借此可以研究一下責任鏈設計模式,這樣就理解了委派的原理。那么哪些場景我們可以使用責任鏈設計模式呢?多思考,才是學習的目的和精髓所在。學到的東西,能用在工作中,才是王道。

一、什么是雙親委派機制

我們先來看一個案例: 打印引導類加載器, 擴展類加載器, 應用程序類加載器加載的目錄

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目錄下的類, 其他目錄下的類事實上基本不會加載. 為什么呢? 這是因為雙親委派機制.

上面這個圖就是雙親委派機制的圖. 這也是類加載的原理。一共分為兩部分:

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

就以自定義的java.lxl.jvm.Math類為例,我們來看看這個類是如何被類加載器加載的。

第一步: 首先是由應用程序類加載器去查找java.lxl.jvm.Math類, 他要去看他已經加載的類中是否有這個類, 如果有, 就直接返回, 如果沒有, 就去加載這個類,但是不是由應用程序類加載器直接加載。而是委托他的父類也就是擴展類加載器去加載。

第二步:擴展類加載器也是先搜索,查看已經加載的類是否有java.lxl.jvm.Math, 如果有就返回,如果沒有就加載這個類。在加載的時候,也不是由自己來加載,而是委托他的父類,引導類加載器去加載。

第三步:引導類加載器先查找已經加載的類中是否有這個類,有則返回,沒有就去加載這個類。這時候, 我們都知道, Math類是我自己定義的, 引導類加載器中不可能有, 加載失敗,所以, 他就會去加載這個類。回去掃描/lib/jar包中有沒有這個類,發現沒有,於是讓擴展類加載器去加載, 擴展類加載器會去掃描擴展包lib/jar/ext包,里面有沒有呢? 當然也沒有, 於是委托應用程序類加載器, ok, 應用程序類加載器是有的, 於是就可以加載了, 然后返回這個類。

【通過分析,我們可以得出,雙親委派機制的實現使用的是責任鏈設計模式。】

那么, 這里有一個問題, 那就是, 由應用程序類加載器首先加載, 然后最后又回到了應用程序類加載器. 繞了一圈又回來了, 這樣是不是有些多此一舉呢, 循環了兩次? 為什么一定要從應用程序類加載器加載呢? 直接從引導類加載器加載不好么?只循環一次啊....

其實, 對於我們的項目來說, 95%的類都是我們自己寫的, 因此, 而我們自己寫的類是有應用程序類加載器加載. 其實,應用程序類加載器只有在第一次的時候, 才會加載兩次. 以后, 當再次使用到這個類的時候, 直接去問應用程序類加載器, 有這個類么? 已經有了, 就直接返回了.

二、 源碼分析雙親委派機制

還是從這張圖說起,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類

然后運行Math的main方法,我們來看一下這個類到底是如何被加載的

 

啟動debug調試模式, 首先進入了Launch.AppClassLoader.loadClass(....)方法

我們來具體看看這個方法的實現

上面都是在做權限校驗, 我們看重點代碼.

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查看集成關系圖

我們看到AppClassLoader繼承自URLClassLoader, 而URLClassLoader又繼承了上面四個類,最終有繼承一個叫做ClassLoader的類, 所有的類加載器, 最終都要繼承這個ClassLoader類.

而這里調用的是super.loadClass(),我們來看看URLClassLoader中是否有loadClass()類, 看過之后發現,他沒有, 最終這個super.loadClass()是繼承了ClassLoader類的loadClass(....)方法

正是這個類實現了雙親委派機制, 下面我們就來看看, 他到底是怎么實現的?

當前的類加載器是AppClassLoader類加載器, 首先第一步是查找AppClassLoader中已經加載的類中,有沒有這個類, 我們看到這里有檢查了一遍。

通過調用findLoadedClass(name)方法來查詢已經加載的類中, 有沒有com.lxl.jvm.Math類. 那么findLoadedClass(name)里面做了什么呢? 我們進去看看

我們看到, findLoaderClass(name)方法調用了自己的一個方法findLoadedClass0, 這個方法是native的, 也就是是本地方法, 使用c++實現的, 我們不能看到底部的具體實現細節了. 但是大致的邏輯就是在已經加載的類中查找有沒有com.lxl.jvm.Math這個類, 如果有就返回Class類信息.

debug看到,顯然是沒有的, 接下來就是走到if(c == null)里面了, 這里做了什么事呢?

 

他判斷了,當前這個類加載器的parent是否是null. 我們知道當前這個類加載是 AppClassLoader, 他的parent是ExtClassLoader, 自然不是null, 所以, 就會執行里面的parent.loadClass(name, false);

2. 從ExtClassLoader中加載目標類

也就是執行擴展類加載器的loadClass(...)方法. 我們來看看擴展類 ExtClassLoader

我們發現ExtClassLoader類里面沒有loadClass(...)方法, 那他沒有, 肯定就是在父類里定義的了, 通過查找, 最后我們發現這個方法還是ClassLoader里的loadClass(...)方法. 於是,我們繼續debug.肯定會再次走到loadClass(...)這個方法里來. 而此時, loadClass是ExtClassloader的loadClass(...)方法

果然, 又走到這個方法里面來了

繼續往下執行, 首先查找ExtClassLoader中已經加載的類中,是否有java.lxl.jvm.Math類, 過程和上面是一樣的. 最后調用的是本地方法.

我們知道, 這肯定是沒有的了. 然后繼續判斷, ExtClassLoader的parent是否為空. 很顯然, 他就是空啊, 因為ExtClassLoader的父類加載器是引導類加載器BootStrapClassLoader, 而引導類加載器是c++寫的,所以,這里的parent為空. parent為空執行的是else中的代碼

3.從BootStrapClassLoader中查找

這個方法就是去引導類加載器BootstrapClassLoad中查找, 是否有這個類, 我們來看看引導類加載器里面的具體實現

我們發現, 最后具體的邏輯也是一個本地方法實現的. 我們還是猜測一下, 這就是去查找引導類加載器已經加載的類中有沒有com.lxl.jvm.Math, 如果有就返回這個類, 如果沒有就返回null.

很顯然, 是沒有的. c == null. 我們繼續來看下面的代碼

到此為止, 我們第一次向上查找的過程就完完事了. 用圖表示就是這樣

首先有應用程序類加載器加載類, 判斷應用程序已加載的類中, 是否有這個類, 結果是沒有, 沒有則調用其父類加載器ExtClassLoader的loadClass()方法, 去擴展類加載器中查找是否有這個類, 也沒有. 那么判斷其父類是否為空, 確實為空, 則進入到引導類加載器中取查找是否有這個類, 最后引導類加載器中也沒有, 返回null

2.2 類加載器向下委派加載

下面來看看類加載器是如何向下委派的?

1.啟動類加載器加載目標類

引導類加載器中也沒有這個類, 返回null, 這里的返回空包含了兩個步驟,一個是查找,沒找到,二是沒找到后去/lib/jar目錄下加載這個類,也沒有加載到。最后返回null。然后回到ExtClassLoader.loadClass(...).

2.擴展類加載器加載目標類

接下來調用findClass(name);查找ExtClassLoader中是否有com.lxl.jvm.Math, 我們來看看具體的實現. 首先這是誰的方法呢?是ExtClassLoader的.

進入到findClass(name)方法中, 首先看看ExtClassLoader類中是否有這個方法, 沒有, 這里調用的是父類UrlClassLoader中的findClass()方法

 

在findClass()里面, 我們看到將路徑中的.替換為/,並在后面增加了.class. 這是在干什么呢? 是將com.lxl.jvm.Math替換為com/lxl/jvm/Math.class,這就是類路徑

然后去resource庫中查找是否有這個路徑. 沒有就返回null, 有就進入到defineClass()方法.

我們想一想, 在ExtClassLoader類路徑里面能找到這個類么?顯然是找不到的, 因為這個類使我們自己定義的.

他們他一定執行return null.

正如我們分析, debug到了return null; 這時執行的ExtClassLoader的findClass(). 返回null, 回到AppClassLoader加載類里面

3.應用程序類加載器加載目標類

c就是null, 然后繼續執行findClass(name), 這時還是進入到了URLClassPath類的findClass(name)

如上圖, 此時調用的是AppClassLoader的findClass(name), 此時的resource還是空么?當然不是了, 在target目錄中就有Math.class類, 找到了, 接下來執行defineClass(name,res)

defindClass這個方法是干什么的呢? 這個方法就是加載類. 類已經找到了, 接下來要做的就是將其加載進來了.

類加載的四個步驟

defindClass()這個類執行的就是類加載的過程。 也就是下圖中的四個步驟:驗證->准備->解析->初始化。如下圖紅線圈出的部分.

再看看這四個步驟:

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中就已經有了, 直接就返回了.

在來看一遍雙親委派機制的流程圖

三、為什么要有雙親委派機制?

兩個原因: 1. 沙箱安全機制, 自己寫的java.lang.String.class類不會被加載, 這樣便可以防止核心API庫被隨意修改 2. 避免類重復加載. 比如之前說的, 在AppClassLoader里面有java/jre/lib包下的類, 他會加載么? 不會, 他會讓上面的類加載器加載, 當上面的類加載器加載以后, 就直接返回了, 避免了重復加載.

我們來看下面的案例

加入, 我在本地定義了一個String類, 包名是java.lang.String. 也就是是rt.jar包下的String類的包名是一樣的哈.

如上圖, 這是我們運行main方法, 會怎么樣? 沒錯, 會報錯

下面分析一下, 為什么會報錯呢?

還是看雙親委派機制的流程, 首先由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來加載。除非手動指定使用其他類加載器加載。也就是說,類里面調用的其他的類都會委托當前的類加載器加載。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM