1.2 雙親委派機制及其原理


1. 類加載的過程

  1.1 類加載器初始化的過程

  1.2 類加載的過程

  1.3 類的懶加載

2. jvm核心類加載器

參考博客: https://www.cnblogs.com/ITPower/p/13197220.html


 

一. 雙親委派機制

1.1 什么是雙親委派機制 

我們先來看一個案例:

復制代碼
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目錄下的類, 其他目錄下的類基本上不會加載. 為什么呢? 這就是下面要說的雙親委派機制.

 

 上面這個圖就是雙親委派機制的圖. 什么意思呢?

比如: 我現在有一個自定義的java.lxl.jvm.Math類. 首先是由應用程序類加載器去加載java.lxl.jvm.Math類, 他要去看他已經加載的類中是否有這個類, 如果有, 就直接返回回來, 如果沒有, 就委托擴展類加載器去加載. 擴展類加載器去查看已經加載的類是否有java.lxl.jvm.Math, 如果有就返回,如果沒有就繼續委托它的父類引導類加載器去加載. 這時候, 我們都知道, Math類是我自己定義的, 引導類加載器中不可能有, 所以, 他就會讓擴展類加載器去加載, 擴展類加載器中有沒有呢? 當然也沒有, 於是委托應用程序類加載器, ok,應用程序類加載器是有的, 於是就可以加載, 然后返回了.

 

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

 

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

 

 1.2 源碼分析雙親委派機制

我們來看一下類加載器.類加載器主要調用的是classLoader.loadClass("com.lxl.Math") 這個方法來實現雙親委派機制的. 根據上面的分析, 我們知道, 在Launcher類初始化的時候, loadClass是AppClassLoader, 那么也就是說, 雙親委派機制的起點是AppClassLoader. 

下面我們來看一下源碼, 我們采用斷點的方式來分析 

 首先, 我們在Launcher的AppClassLoader的loadClass(String var1, boolean var2) 這個方法添加一個斷點, 並將其賦值為我們的com.lxl.jvm.Math類

 

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

 

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

 

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

 

上面都是在做權限校驗, 我們看重點代碼. 重點代碼是調用了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);

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

 

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

 

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

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

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

 

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

 

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

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

 

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

 

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

 

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

引導類加載器中也沒有這個類, 返回null, 接下來調用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加載類里面

 

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

 

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

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

這里執行的就是我們之前說的類加載的幾個步驟,如下圖紅線圈出的部分.

 好了,具體的就不說了. 后面有時間在繼續分析.

這就是雙親委派機制的源碼. 

那么當下一次在遇到com.lxl.jvm.Math類的時候, 我們在AppClassLoader中就已經有了, 直接就返回了.

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

 

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

兩個原因: 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包下的類, 他會加載么? 不會, 他會讓上面的類加載器加載, 當上面的類加載器加載以后, 就直接返回了, 避免了重復加載.

 


免責聲明!

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



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