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

JVM類加載器之ClassLoader的使用詳解

 更新時間:2022年10月11日 16:43:41   作者:丨Jack_Chen丨  
類加載器負責(zé)讀取Java字節(jié)代碼,并轉(zhuǎn)換成java.lang.Class類的一個實例的代碼模塊。本文主要和大家聊聊JVM類加載器ClassLoader的使用,需要的可以了解一下

類加載器

概述

類加載器負責(zé)讀取Java字節(jié)代碼,并轉(zhuǎn)換成java.lang.Class類的一個實例的代碼模塊。

類加載器除了用于加載類外,還可用于確定類在Java虛擬機中的唯一性。

任意一個類,都由加載它的類加載器和這個類本身一同確定其在 Java 虛擬機中的唯一性,每一個類加載器,都有一個獨立的類名稱空間,而不同類加載器中是允許同名(指全限定名相同)類存在的。

比較兩個類是否“相等”,前提是這兩個類由同一個類加載器加載,否則,即使這兩個類來源于同一個Class 文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那么這兩個類就必定不相等。

這里“相等”是指:類的Class對象的equals()方法、isInstance()方法的返回結(jié)果,使用instanceof關(guān)鍵字做對象所屬關(guān)系判定等情況。

加載器的種類

1.啟動類加載器:Bootstrap ClassLoader

最頂層的加載類,由 C++實現(xiàn),負責(zé)加載%JAVA_HOME%/lib目錄下的jar包和類或者被 -Xbootclasspath參數(shù)指定的路徑中的所有類。

2.拓展類加載器:Extension ClassLoader

負責(zé)加載java平臺中擴展功能的一些jar包,如加載%JRE_HOME%/lib/ext目錄下的jar包和類,或-Djava.ext.dirs所指定的路徑下的jar包。

3.系統(tǒng)類加載器/應(yīng)用程序加載器:App ClassLoader

負責(zé)加載當前應(yīng)用classpath中指定的jar包及-Djava.class.path所指定目錄下的類和jar包。開發(fā)者可以直接使用這個類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

4.自定義類加載器:Custom ClassLoader

通過java.lang.ClassLoader的子類自定義加載class,屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會根據(jù)j2ee規(guī)范自行實現(xiàn)ClassLoader

驗證不同加載器

每個類加載都有一個父類加載器,可以通過程序來驗證

    public static void main(String[] args) {
        // App ClassLoader
        System.out.println(new User().getClass().getClassLoader());
        // Ext ClassLoader
        System.out.println(new User().getClass().getClassLoader().getParent());
        // Bootstrap ClassLoader
        System.out.println(new User().getClass().getClassLoader().getParent().getParent());
        // Bootstrap ClassLoader
        System.out.println(new String().getClass().getClassLoader());
    }

AppClassLoader的父類加載器為ExtClassLoader, ExtClassLoader的父類加載器為 null,null 并不代表ExtClassLoader沒有父類加載器,而是 BootstrapClassLoader 。

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@5fdef03a
null
null

核心方法

查看類ClassLoader的loadClass方法

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            // 檢查類是否已經(jīng)加載
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	// 父加載器不為空,調(diào)用父加載器loadClass()方法處理
                    if (parent != null) {
                    	// 讓上一層加載器進行加載
                        c = parent.loadClass(name, false);
                    } else {
                    	// 父加載器為空,使用啟動類加載器 BootstrapClassLoader 加載
                        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();
                    // 調(diào)用此類加載器所實現(xiàn)的findClass方法進行加載
                    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方法是當字節(jié)碼加載到內(nèi)存后進行鏈接操作,對文件格式和字節(jié)碼驗證,并為 static 字段分配空間并初始化,符號引用轉(zhuǎn)為直接引用,訪問控制,方法覆蓋等
                resolveClass(c);
            }
            return c;
        }
    }

JVM類加載機制的三種方式

全盤負責(zé)

當一個類加載器負責(zé)加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責(zé)載入,除非顯示使用另外一個類加載器來載入

注意:

系統(tǒng)類加載器AppClassLoader加載入口類(含有main方法的類)時,會把main方法所依賴的類及引用的類也載入。只是調(diào)用了ClassLoader.loadClass(name)方法,并沒有真正定義類。真正加載class字節(jié)碼文件生成Class對象由雙親委派機制完成。

父類委托、雙親委派

父類委托即雙親委派,雙親委派模型是描述類加載器之間的層次關(guān)系。它要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)當有自己的父類加載器。父子關(guān)系一般不會以繼承的關(guān)系實現(xiàn),而是以組合關(guān)系來復(fù)用父加載器的代碼。

雙親委派模型是指:子類加載器如果沒有加載過該目標類,就先委托父類加載器加載該目標類,只有在父類加載器找不到字節(jié)碼文件的情況下才從自己的類路徑中查找并裝載目標類。

雙親委派模型的好處

保證Java程序的穩(wěn)定運行,避免類的重復(fù)加載:JVM區(qū)分不同類的方式不僅僅根據(jù)類名,相同的類文件被不同的類加載器加載產(chǎn)生的是兩個不同的類

保證Java核心API不被篡改:如果沒有使用雙親委派模型,而是每個類加載器加載自己的話就會出現(xiàn)一些問題,如編寫一個稱為java.lang.Object 類,程序運行時,系統(tǒng)就會出現(xiàn)多個不同的Object類。反之使用雙親委派模型:無論使用哪個類加載器加載,最終都會委派給最頂端的啟動類加載器加載,從而使得不同加載器加載的Object類都是同一個。

雙親委派機制加載Class的具體過程:

1. ClassLoader先判斷該Class是否已加載,如果已加載,則返回Class對象,如果沒有則委托給父類加載器

2. 父類加載器判斷是否加載過該Class,如果已加載,則返回Class對象,如果沒有則委托給祖父類加載器

3. 依此類推,直到始祖類加載器(引用類加載器)

4. 始祖類加載器判斷是否加載過該Class,如果已加載,則返回Class對象

如果沒有則嘗試從其對應(yīng)的類路徑下尋找class字節(jié)碼文件并載入

如果載入成功,則返回Class對象;如果載入失敗,則委托給始祖類加載器的子類加載器

5. 始祖類加載器的子類加載器嘗試從其對應(yīng)的類路徑下尋找class字節(jié)碼文件并載入

如果載入成功,則返回Class對象;如果載入失敗,則委托給始祖類加載器的孫類加載器

6. 依此類推,直到源ClassLoader

7. 源ClassLoader嘗試從其對應(yīng)的類路徑下尋找class字節(jié)碼文件并載入

如果載入成功,則返回Class對象;如果載入失敗,源ClassLoader不會再委托其子類加載器,而是拋出異常

注意:

雙親委派機制是Java推薦的機制,并不是強制的機制。可以繼承java.lang.ClassLoader類,實現(xiàn)自己的類加載器。如果想保持雙親委派模型,應(yīng)該重寫findClass(name)方法;如果想破壞雙親委派模型,可以重寫loadClass(name)方法。

緩存機制

緩存機制將會保證所有加載過的Class都將在內(nèi)存中緩存,當程序中需要使用某個Class時,類加載器先從內(nèi)存的緩存區(qū)尋找該Class,只有緩存區(qū)不存在,系統(tǒng)才會讀取該類對應(yīng)的二進制數(shù)據(jù),并將其轉(zhuǎn)換成Class對象,存入緩存區(qū)。

對于一個類加載器實例來說,相同全名的類只加載一次,即loadClass方法不會被重復(fù)調(diào)用。因此,這就是為什么修改Class后,必須重啟JVM,程序的修改才會生效的原因。

JDK8使用的是直接內(nèi)存,所以會用到直接內(nèi)存進行緩存。因此,類變量為什么只會被初始化一次的原因。

打破雙親委派

在加載類的時候,會一級一級向上委托,判斷是否已經(jīng)加載,從自定義類加載器 --> 應(yīng)用類加載器 --> 擴展類加載器 --> 啟動類加載器,如果到最后都沒有加載這個類,則回去加載自己的類。

雙親委派模型并不是強制模型,而且會帶來一些些的問題。例如:java.sql.Driver類,JDK只能提供一個規(guī)范接口,而不能提供實現(xiàn)。提供實現(xiàn)的是實際的數(shù)據(jù)庫提供商,提供商的庫不可能放JDK目錄里。

重寫loadclass方法

自定義類加載,重寫loadclass方法,即可破壞雙親委派機制

因為雙親委派的機制都是通過這個方法實現(xiàn)的,這個方法可以指定類通過什么類加載器來進行加載,所有如果改寫加載規(guī)則,相當于打破雙親委派機制

import cn.ybzy.demo.Test;

import java.io.*;

public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData;
        try {
            classData = loadClassData(name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        String replace = className.replace('.', File.separatorChar);
        String path = ClassLoader.getSystemResource("").getPath() + replace + ".class";
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            inputStream = new FileInputStream(path);
            byteArrayOutputStream = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }

        return null;
    }

    @Override
    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 {
                    // 修改classloader的原雙親委派邏輯,從而打破雙親委派
                    if (name.startsWith("cn.ybzy.demo")) {
                        c = findClass(name);
                    } else {
                        c = this.getParent().loadClass(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;
        }
    }
}
    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> aClass = classLoader.loadClass(Test.class.getName());
        System.out.println(aClass.getClassLoader());
    }
cn.ybzy.demo.MyClassLoader@2f410acf

自定義類加載器

自定義類加載器的核心在于對字節(jié)碼文件的獲取,如果是加密的字節(jié)碼則需要在類中對文件進行解密。

準備字節(jié)碼文件

創(chuàng)建Test類,同時進行javac Test.class編譯成字節(jié)碼文件,放到目錄下:D:\Temp\cn\ybzy\demo

package cn.ybzy.demo;

public class Test {
    public static void main(String[] args) {
        System.out.println("Test...");
    }
}

創(chuàng)建自定義類加載器

import java.io.*;

public class MyClassLoader extends ClassLoader {
    private String root;

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData;
        try {
            classData = loadClassData(name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        String fileName = root + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            inputStream = new FileInputStream(fileName);
            byteArrayOutputStream = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }

        return null;
    }

    public String getRoot() {
        return root;
    }

    public void setRoot(String root) {
        this.root = root;
    }
}

執(zhí)行測試

啟動main方法,執(zhí)行測試

    public static void main(String[] args) {
        MyClassLoader classLoader = new MyClassLoader();
        classLoader.setRoot("D:\\Temp");
        Class<?> testClass = null;
        try {
            testClass = classLoader.loadClass("cn.ybzy.demo.Test");
            Object object = testClass.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
cn.ybzy.demo.MyClassLoader@5679c6c6

將Test類放到項目類路徑下,由于雙親委托機制的存在,會直接導(dǎo)致該類由 AppClassLoader 加載,而不會通過自定義類加載器來加載

sun.misc.Launcher$AppClassLoader@18b4aac2

注意事項

1、這里傳遞文件名需要是類的全限定性名稱,因為defineClass方法是按這種方式/格式進行處理

因此,若沒有全限定名,需要將類的全路徑加載進去

2、不要重寫loadClass方法,因為這樣容易破壞雙親委托模式

3、Test類本身可以被AppClassLoader類加載,因此不能把Test.class放在類路徑下

否則,由于雙親委托機制的存在,會直接導(dǎo)致該類由AppClassLoader加載,而不會通過自定義類加載器來加載

以上就是JVM類加載器之ClassLoader的使用詳解的詳細內(nèi)容,更多關(guān)于JVM類加載器ClassLoader的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 一文解開java中字符串編碼的小秘密(干貨)

    一文解開java中字符串編碼的小秘密(干貨)

    這篇文章主要介紹了一文解開java中字符串編碼的小秘密(干貨),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java TreeMap升序|降序排列和按照value進行排序的案例

    Java TreeMap升序|降序排列和按照value進行排序的案例

    這篇文章主要介紹了Java TreeMap升序|降序排列和按照value進行排序的案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • Java?定時任務(wù)技術(shù)趨勢詳情

    Java?定時任務(wù)技術(shù)趨勢詳情

    這篇文章主要介紹了Java?定時任務(wù)技術(shù)趨勢詳情,定時任務(wù)是每個業(yè)務(wù)常見的需求,比如每分鐘掃描超時支付的訂單,每小時清理一次數(shù)據(jù)庫歷史數(shù)據(jù),每天統(tǒng)計前一天的數(shù)據(jù)并生成報表等,下文更多相關(guān)資料,需要的小伙伴可以參考一下
    2022-05-05
  • SpringBoot整合Druid數(shù)據(jù)庫連接池的方法

    SpringBoot整合Druid數(shù)據(jù)庫連接池的方法

    Druid是Java語言中最好的數(shù)據(jù)庫連接池。Druid能夠提供強大的監(jiān)控和擴展功能。這篇文章主要介紹了SpringBoot整合Druid數(shù)據(jù)庫連接池的方法,需要的朋友可以參考下
    2020-07-07
  • C# log4net使用案例詳解

    C# log4net使用案例詳解

    這篇文章主要介紹了C# log4net使用案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Java利用Poi讀取excel并對所有類型進行處理

    Java利用Poi讀取excel并對所有類型進行處理

    這篇文章主要為大家詳細介紹了Java利用Poi讀取excel并對所有類型進行處理的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下
    2024-01-01
  • 利用反射實現(xiàn)Excel和CSV 轉(zhuǎn)換為Java對象功能

    利用反射實現(xiàn)Excel和CSV 轉(zhuǎn)換為Java對象功能

    將Excel或CSV文件轉(zhuǎn)換為Java對象(POJO)以及將Java對象轉(zhuǎn)換為Excel或CSV文件可能是一個復(fù)雜的過程,但如果使用正確的工具和技術(shù),這個過程就會變得十分簡單,在本文中,我們將了解如何利用一個Java反射的庫來實現(xiàn)這個功能,需要的朋友可以參考下
    2023-11-11
  • java 二進制數(shù)據(jù)與16進制字符串相互轉(zhuǎn)化方法

    java 二進制數(shù)據(jù)與16進制字符串相互轉(zhuǎn)化方法

    今天小編就為大家分享一篇java 二進制數(shù)據(jù)與16進制字符串相互轉(zhuǎn)化方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • Java異步編程的5種異步實現(xiàn)方式詳解

    Java異步編程的5種異步實現(xiàn)方式詳解

    這篇文章主要介紹了Java異步編程的5種異步實現(xiàn)方式詳解,異步編程是程序并發(fā)運行的一種手段,它允許多個事件同時發(fā)生,當程序調(diào)用需要長時間運行的方法時,它不會阻塞當前的執(zhí)行流程,程序可以繼續(xù)運行,需要的朋友可以參考下
    2024-01-01
  • SpringBoot讀取Resource目錄下文件的四種方式總結(jié)

    SpringBoot讀取Resource目錄下文件的四種方式總結(jié)

    在Spring?Boot項目中,經(jīng)常需要獲取resources目錄下的文件,這些文件可以包括配置文件、模板文件、靜態(tài)資源等,本文將介紹四種常用的方法來獲取resources目錄下的文件,需要的朋友可以參考下
    2023-08-08

最新評論

呼图壁县| 封开县| 松溪县| 沙湾县| 珲春市| 行唐县| 清苑县| 汝阳县| 张家界市| 江油市| 大连市| 抚远县| 河间市| 镇安县| 黄山市| 梁山县| 延川县| 社会| 梅州市| 遂平县| 四川省| 崇左市| 广水市| 许昌县| 福安市| 昌平区| 饶阳县| 木里| 潼南县| 靖西县| 安平县| 淅川县| 柘城县| 凤城市| 祁阳县| 嘉黎县| 瑞昌市| 西峡县| 信丰县| 宁南县| 金华市|