最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java中類加載與雙親委派機制詳解

 更新時間:2022年08月19日 09:09:05   作者:超的博客  
這篇文章主要介紹了java中類加載與雙親委派機制詳解,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下

類加載是什么

把磁盤中的java文件加載到內(nèi)存中的過程叫做類加載

當我們用java命令運行某個類的main函數(shù)啟動程序時,首先需要通過類加載器把主類加載到JVM. 有如下 User 類

package dc.dccmmtop;
public Class User {
    public static void main(String[] args) {
        System.out.println("hello");
    }
}

運行 java dc.dccmmtop.User 時, 先要找到 User.Class 文件,查找全路徑就是 Class_PATH + {{package name}},對于User類來說,就是 {$Class_APTH}/dc/dccmmtop.User.Class

假如 User.java 在F:\code, 并且不在Class_PATH 下,可以通過 java -Classpath "F:\code" 臨時指定。

加載類之后還有后續(xù)的步驟:

  • 驗證
  • 準備
  • 解析
  • 初始化
  • 使用
  • 卸載

這篇文章主要來講講類加載

類加載器

不了解類加載機制的,可能就認為,只需找到j(luò)ava文件所在的磁盤位置,然后進行一次讀文件的操作不就完成了加載嘛,其實遠非如此。

總有一個加載類的工具,這個工具叫做類加載器,在java代碼中可以通過如下方式獲取當前類的類加載器是什么

package dccmmtop;  
  
public Class User {  
    public static void main(String[] args) {  
        System.out.println("hello");  
        System.out.println(User.Class.getClassLoader()); 
    }  
}

如圖可以看到類加載器的名字叫做 AppClassLoader

我們?nèi)炙阉饕幌逻@個類,會發(fā)現(xiàn)在 sun.misc.Launcher.java 文件中找到。 

那么這個AppClassLoader 本身也是一個 java 文件,它又是什么時候被加載并初始化的呢?

我們滾動到文件頂部,看到 Launcher 類的構(gòu)造方法部分:

標記1 和標記2 實現(xiàn)了一個單例模式,在5 處獲取到了 AppClassLoader 實例。也就是說在某一個地方通過調(diào)用 Launcher 類中的 getLauncher() 方法,會得到 AppClassLoader 實例, 那么 getLauncher() 方法又是在哪里調(diào)用的呢?追蹤到這里已經(jīng)無法在java代碼中找到上一步了,其實這個方法是jvm (c++實現(xiàn))調(diào)用的,如下圖:

以上就是類加載的主要步驟了。下面看一下雙親委派機制

雙親委派機制

我們繼續(xù)看AppClassLoader 實例化的過程:

在5處,實例化了一個AppClassLoader的對象,同時傳進去了一個參數(shù) var1, 這個 var1 是另外一個類加載器ExtClassLoader , 我們在進入 getAppClassLoader 方法看一看是怎么實現(xiàn)的:

先看一下 幾個ClassLoad的繼承關(guān)系:

有上面的繼承關(guān)系圖可以看出來,AppClassLoader 和 ExtClassLoader 都是從 ClassLoader 繼承來的。

在 Launcher() 中可知,調(diào)用 AppClassLoader.getAppClassLoader() 方法時, 把 ExtClassLoader 的實例作為參數(shù)傳遞進來,最終到4這一步,作為 var2 參數(shù),調(diào)用父類的構(gòu)造方法,繼續(xù)追蹤父類的構(gòu)造方法直到 ClassLoader : 

在 ClassLoader 構(gòu)造方法中,維護了一個 parent 變量,到此我們知道了 AppClassLoader 中 parent 變量保存的是 ExtClassLoader的實例, 如下圖表示

繼續(xù)看Launcher 構(gòu)造方法: 

loadClass() 方法將 Class 文件加載到j(luò)vm中,我們跟蹤一下這個方法,會發(fā)現(xiàn)最后會調(diào)到 根類ClassLoader 中:

protected Class<?> loadClass(String name, boolean resolve)  
    throws ClassNotFoundException  
{  
    synchronized (getClassLoadingLock(name)) {  
        // First, check if the Class has already been loaded  
        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) {  
                // ClassNotFoundException thrown if Class not found  
                // from the non-null parent Class loader            }  
  
            if (c == null) {  
                // If still not found, then invoke findClass in order  
                // to find the Class.                long t1 = System.nanoTime();  
                c = findClass(name);  
  
                // this is the defining Class loader; record the stats  
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);  
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);  
                sun.misc.PerfCounter.getFindClasses().increment();  
            }  
        }  
        if (resolve) {  
            resolveClass(c);  
        }  
        return c;  
    }  
}

上面代碼塊中的弟6行,findLoadedClass() , 先從已加載到的類集合中查找有沒有這個類,如果有的話,直接返回,沒有再進行下一步, findLoadedClass 方法源碼如下:

到 native finnal Class<?> findLoadedClass0(String name); 這里已經(jīng)無法在向后追蹤了,看到 naive ,要明白 使用native關(guān)鍵字說明這個方法是原生函數(shù),也就是這個方法是用C/C++語言實現(xiàn)的,并且被編譯成了DLL,由java去調(diào)用.

此時 User.Class 是第一次加載,AppClassLoader 中肯定無法在已加載的集合中找到,所以繼續(xù)向下走到第 10,11 行. 上面已經(jīng)分析過,AppClassLoader 中的 parent 是 ExtClassLoader , 所以在11行由 ExtClassLoader 的實例執(zhí)行 laodClass 方法。 ExtClassLoader 沒有覆寫根類ClassLoader 的loaderClass 方法,所以也會到這里,只不過 ExtClassLoader 的 parent 是 NUll, 會走到13行,調(diào)用findBootstrapClassOrNull() 方法,再看一下這個方法的實現(xiàn):

 會發(fā)現(xiàn)這個方法也是C++實現(xiàn)的,雖然我們無法看到源碼,但是根據(jù)注釋可以知道,這個是保存了啟動類加載器加載過的類。

到此為止,我們已經(jīng)見識過3中不同的類加載器了:

  • AppClassLoader
  • ExtClassLoader
  • BootStrapClassLoader

我們先不管這個后面兩個類加載器是什么, 假定他們也找不到 User.Class. 繼續(xù)向下看:

執(zhí)行到第21行findClas()這里,再看源碼 

在A-2 這一步,ucp 其實保存的就是當前 ClassLoader 的類加載路徑,就不再展開。要記住此時的 ClassLoader 是 ExtClassLoader, 假如仍然找不到User.Class 會執(zhí)行到 A-3.然后返回到 loadClass 方法中, 此時 c 是空,繼續(xù)執(zhí)行到33行,返回到 AppClassLoader 調(diào)用 parent.getAppClassLoader 處,在 AppClassLoader 實例的范圍下繼續(xù)向后執(zhí)行,然后再繼續(xù)調(diào)用 findClass 方法,如果在AppClassLoader的類加載路徑中找到User.Class 文件,就會 執(zhí)行 defindClass(name,res) 方法去加載類文件了。

整個過程用文字描述起來比較復(fù)雜,來張圖就很清楚了,為什么叫做雙親委派: 

把 loadedClassList 集合稱作緩存:

  • 先在 AppClassLoader 中緩存中找,如果找不到向 ExtClassLoader 找,如果能找到,直接返回
  • 在 ExtClassLoader 中緩存找,如果找不到向 BootStrapClassLoader 找,如果能找到,直接返回
  • 在 BootStrapClassLoader 找,如果找不到, 在 ExtClassLoader 類路徑集合中找,
  • 如果在 ExtClassLoader 類路徑集合找不到,在 AppClassLoader 類路徑集合找
  • 如果在 AppClassLoader 類路徑集合中能找到,加載該類,并放入緩存。找不到則報錯

雙親指的是 ExtClassLoader 和 BootStrapClassLoader, AppClassLoader 先不加載,而是向上讓其“父”加載,父加載不到時,自己再加載。這里的父不是父類,而是調(diào)用層級的關(guān)系。

是時候介紹一下 這三個類加載器

BootStrapClassLoader

引導(dǎo)類加載器

負責加載支撐JVM運行的位于JRE的lib目錄下的核心類庫,比如 rt.jar、charsets.jar等

ExtClassLoader

擴展類加載器

負責加載支撐JVM運行的位于JRE的lib目錄下的ext擴展目錄中的JAR 類包

AppClassLoader

應(yīng)用程序加載器

負責加載ClassPath路徑下的類包,主要就是加載你自己寫的那些類

我們可以寫代碼驗證一下:

package dccmmtop;  
import sun.misc.Launcher;  
import java.net.URL;  
public Class User {  
    public static void main(String[] args) {  
        System.out.println(String.Class.getClassLoader()); // null  
        System.out.println(com.sun.crypto.provider.DESKeyFactory.Class.getClassLoader().getClass().getName()); //sun.misc.Launcher$ExtClassLoader  
        System.out.println(User.Class.getClassLoader().getClass().getName()); // sun.misc.Launcher$AppClassLoader
        System.out.println();  
        System.out.println("bootstrapLoader加載以下文件:");
        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")); 
    }
}

輸入如下:

null // 因為調(diào)用了 c++ 實現(xiàn)。無法獲取到j(luò)ava對象
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@77459877
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2

bootstrapLoader加載以下文件:
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/Classes

extClassloader加載以下文件:
C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

appClassLoader加載以下文件:
C:\Program Files\Java\jdk1.8.0_261\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_261\jre\lib\ext\jaccess.jar;...省略

為什么使用雙親委派機制

  • 沙箱安全機制: 自己寫的java.lang.String.Class類不會被加載,這樣便可以防止核心 API庫被隨意篡改
  • 避免類的重復(fù)加載:當父親已經(jīng)加載了該類時,就沒有必要子ClassLoader再加載一 次,保證被加載類的唯一性

全盤負責委托機制

“全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類
所依賴及引用的類也由這個ClassLoder載入

自定義類加載器

從上述源碼的描述可知,類加載器的核心方法是 findClass , 和 defineClass 。

defindClass 將class文件從磁盤加載文件到內(nèi)存,defineClass 開始解析class文件: 

所以自定義類加載器只需繼承 ClassLoader,然后從寫 findClass 文件就行了:

目錄如下: 

App.java:

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class App {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        // 從磁盤加載文件
        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        // 重寫
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                // defineClass將一個字節(jié)數(shù)組轉(zhuǎn)為Class對象,這個字節(jié)數(shù)組是class文件讀取后最終的字節(jié) 數(shù)組。
                return defieClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    }
    public static void main(String args[]) throws Exception {
        // 初始化自定義類加載器,會先初始化父類ClassLoader,其中會把自定義類加載器的父加載 器設(shè)置為應(yīng)用程序類加載器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/dc_code/java"); // D盤創(chuàng)建
        // 創(chuàng)建 io/dc 幾級目錄,將User類的復(fù)制類User.class丟入該目錄
        Class clazz = classLoader.loadClass("io.dc.User");
        Object obj = clazz.newInstance();
        // 使用反射調(diào)用 User 類的 sout 方法
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

打破雙親委派機制

經(jīng)過上面的源碼分析發(fā)現(xiàn),主要是 ClassLoader 類中的laodClass 方法來實現(xiàn)的雙親委派機制,自己不加載而是先讓其父加載。

所以直接復(fù)寫 loadClass 方法即可,不再指定父級加載,當前類直接加載,如下:

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class App {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        // 從磁盤加載文件
        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                // defineClass將一個字節(jié)數(shù)組轉(zhuǎn)為Class對象,這個字節(jié)數(shù)組是class文件讀取后最終的字節(jié) 數(shù)組。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    if (name.startsWith("io.dc")) {
                        // 直接查找, 限定包名
                        c = findClass(name);
                    } else {
                        // 其他包中的類還是使用雙親委派機制
                        // 否則會報找不到 Object 類
                        c = this.getParent().loadClass(name);
                    }
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    }

    public static void main(String args[]) throws Exception {
        // 初始化自定義類加載器,會先初始化父類ClassLoader,其中會把自定義類加載器的父加載 器設(shè)置為應(yīng)用程序類加載器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/dc_code/java"); // D盤創(chuàng)建
        // 創(chuàng)建 io/dc 幾級目錄,將User類的復(fù)制類User.class丟入該目錄
        Class clazz = classLoader.loadClass("io.dc.User");
        Object obj = clazz.newInstance();
        // 使用反射調(diào)用 User 類的 sout 方法
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

到此這篇關(guān)于java中類加載與雙親委派機制詳解的文章就介紹到這了,更多相關(guān)java類加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 為什么JDK8中HashMap依然會死循環(huán)

    為什么JDK8中HashMap依然會死循環(huán)

    這篇文章主要介紹了為什么JDK8中HashMap依然會死循環(huán),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • 希爾排序的算法代碼

    希爾排序的算法代碼

    希爾排序也是一種插入排序方法,實際上是一種分組插入方法。
    2013-03-03
  • Java遍歷Map鍵、值和獲取Map大小的方法示例

    Java遍歷Map鍵、值和獲取Map大小的方法示例

    本篇文章主要介紹了Java遍歷Map鍵、值和獲取Map大小的方法示例,詳細的介紹了Java遍歷Map的兩種實現(xiàn)方法和大小,具有一定的參考價值,有興趣的可以了解一下。
    2017-01-01
  • Spring的連接數(shù)據(jù)庫以及JDBC模板(實例講解)

    Spring的連接數(shù)據(jù)庫以及JDBC模板(實例講解)

    下面小編就為大家?guī)硪黄猄pring的連接數(shù)據(jù)庫以及JDBC模板(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • Spring依賴注入多種類型數(shù)據(jù)的示例代碼

    Spring依賴注入多種類型數(shù)據(jù)的示例代碼

    這篇文章主要介紹了Spring依賴注入多種類型數(shù)據(jù),本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • JAVA簡單選擇排序算法原理及實現(xiàn)

    JAVA簡單選擇排序算法原理及實現(xiàn)

    選擇排序(Selection Sort )分為兩種 簡單選擇排序(Simple Selection Sort) 和樹形選擇排序
    2014-01-01
  • mybatis查詢語句揭秘之封裝數(shù)據(jù)

    mybatis查詢語句揭秘之封裝數(shù)據(jù)

    這篇文章主要給大家介紹了關(guān)于mybatis查詢語句揭秘之封裝數(shù)據(jù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用mybatis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 在@Value注解內(nèi)使用SPEL自定義函數(shù)方式

    在@Value注解內(nèi)使用SPEL自定義函數(shù)方式

    這篇文章主要介紹了在@Value注解內(nèi)使用SPEL自定義函數(shù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java StackOverflowError詳解

    Java StackOverflowError詳解

    這篇文章主要介紹了Java StackOverflowError詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 淺談Redis的key和value大小限制

    淺談Redis的key和value大小限制

    這篇文章主要介紹了淺談Redis的key和value大小限制,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11

最新評論

平利县| 丰顺县| 彰武县| 渭源县| 壶关县| 汾阳市| 姜堰市| 彭山县| 泽州县| 清新县| 怀柔区| 旺苍县| 黄骅市| 保山市| 三河市| 西宁市| 兖州市| 府谷县| 上林县| 大同县| 开鲁县| 普定县| 郎溪县| 通辽市| 阳城县| 新和县| 桑日县| 镇康县| 麻阳| 勃利县| 土默特左旗| 张家界市| 陇川县| 交城县| 新野县| 郧西县| 开江县| 荣昌县| 炉霍县| 宁乡县| 山阳县|