偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

你了解 Java 的類加載器嗎?類加載機制是什么?什么是雙親委派機制?

開發(fā) 前端
Java 的類加載器機制與雙親委派模型是 Java 虛擬機(JVM)加載類文件時采用的一種體系結(jié)構(gòu)。它用于確保 Java 應(yīng)用程序中類的單一性、安全性和加載順序。

什么是類加載器,類加載器有哪些?

實現(xiàn)通過類的全限定名獲取該類的二進制字節(jié)流的代碼塊叫做類加載器。

主要有一下四種類加載器:

  • 啟動類加載器:用來加載 Java 核心類庫,無法被 Java 程序直接引用。
  • 擴展類加載器:它用來加載 Java 的擴展庫。Java 虛擬機的實現(xiàn)會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
  • 系統(tǒng)類加載器:它根據(jù)應(yīng)用的類路徑來加載 Java 類??赏ㄟ^ClassLoader.getSystemClassLoader() 獲取它。
  • 自定義類加載器:通過繼承java.lang.ClassLoader 類的方式實現(xiàn)。

JVM類加載機制?

Java 的類加載器機制與雙親委派模型是 Java 虛擬機(JVM)加載類文件時采用的一種體系結(jié)構(gòu)。它用于確保 Java 應(yīng)用程序中類的單一性、安全性和加載順序。

  • 全盤負責:當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入
  • 緩存機制:緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區(qū)尋找該Class,只有緩存區(qū)不存在,系統(tǒng)才會讀取該類對應(yīng)的二進制數(shù)據(jù),并將其轉(zhuǎn)換成Class對象,存入緩存區(qū)。這就是為什么修改了Class后,必須重啟JVM,程序的修改才會生效
  • 雙親委派機制:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委托給父加載器去完成,依次向上,因此,所有的類加載請求最終都應(yīng)該被傳遞到頂層的啟動類加載器中,只有當父加載器在它的搜索范圍中沒有找到所需的類時,即無法完成該加載,子加載器才會嘗試自己去加載該類。

什么是雙親委派機制?

一個類加載器收到一個類的加載請求時,它首先不會自己嘗試去加載它,而是把這個請求委派給父類加載器去完成,這樣層層委派,因此所有的加載請求最終都會傳送到頂層的啟動類加載器中,只有當父類加載器反饋自己無法完成這個加載請求時,子加載器才會嘗試自己去加載。

圖片圖片

雙親委派模型的具體實現(xiàn)代碼在 java.lang.ClassLoader 中,此類的 loadClass() 方法運行過程如下:先檢查類是否已經(jīng)加載過,如果沒有則讓父類加載器去加載。當父類加載器加載失敗時拋出ClassNotFoundException ,此時嘗試自己去加載。

雙親委派模型目的?

可以防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼。如果沒有雙親委派模型而是由各個類加載器自行加載的話,如果用戶編寫了一個 java.lang.Object 的同名類并放在 ClassPath 中,多個類加載器都去加載這個類到內(nèi)存中,系統(tǒng)中將會出現(xiàn)多個不同的 Object 類,那么類之間的比較結(jié)果及類的唯一性將無法保證。

什么時候需要打破雙親委派模型?

比如類A已經(jīng)有一個classA,恰好類B也有一個clasA 但是兩者內(nèi)容不一致,如果不打破雙親委派模型,那么類A只會加載一次

只要在加載類的時候,不按照UserCLASSlOADER->Application ClassLoader->Extension ClassLoader->Bootstrap ClassLoader的順序來加載就算打破打破雙親委派模型了。比如自定義個ClassLoader,重寫loadClass方法(不依照往上開始尋找類加載器),那就算是打破雙親委派機制了。

打破雙親委派模型的方式?

有兩種方式:

  1. 自定義一個類加載器的類,并覆蓋抽象類java.lang.ClassL oader中l(wèi)oadClass..)方法,不再優(yōu)先委派“父”加載器進行類加載。(比如Tomcat)
  2. 主動違背類加載器的依賴傳遞原則

例如在一個BootstrapClassLoader加載的類中,又通過APPClassLoader來加載所依賴的其它類,這就打破了“雙親委派模型”中的層次結(jié)構(gòu),逆轉(zhuǎn)了類之間的可見性。

典型的是Java SPI機制,它在類ServiceLoader中,會使用線程上下文類加載器來逆向加載classpath中的第三方廠商提供的Service Provider類。(比如JDBC)

什么是依賴傳遞原則?

如果一個類由類加載器A加載,那么這個類的依賴類也是由「相同的類加載器」加載。

Tomcat是如何打破雙親委派模型的?

在Tomcat部署項目時,是把war包放到tomcat的webapp下,這就意味著一個tomcat可以運行多個Web應(yīng)用程序。

假設(shè)現(xiàn)在有兩個Web應(yīng)用程序,它們都有一個類,叫User,并且它們的類全限定名都一樣,比如都是com.yyy.User,但是他們的具體實現(xiàn)是不一樣的。那么Tomcat如何保證它們不會沖突呢?

Tomcat給每個 Web 應(yīng)用創(chuàng)建一個類加載器實例(WebAppClassLoader),該加載器重寫了loadClass方法,優(yōu)先加載當前應(yīng)用目錄下的類,如果當前找不到了,才一層一層往上找,這樣就做到了Web應(yīng)用層級的隔離。

但是并不是Web應(yīng)用程序的所有依賴都需要隔離的,比如要用到Redis的話,Redis就可以再Web應(yīng)用程序之間貢獻,沒必要每個Web應(yīng)用程序每個都獨自加載一份。因此Tomcat就在WebAppClassLoader上加個父加載器ShareClassLoader,如果WebAppClassLoader沒有加載到這個類,就委托給ShareClassLoader去加載。(意思就類似于將需要共享的類放到一個共享目錄下)

Web應(yīng)用程序有類,但是Tomcat本身也有自己的類,為了隔絕這兩個類,就用CatalinaClassLoader類加載器進行隔離,CatalinaClassLoader加載Tomcat本身的類

Tomcat與Web應(yīng)用程序還有類需要共享,那就再用CommonClassLoader作為CatalinaClassLoader和ShareClassLoader的父類加載器,來加載他們之間的共享類

Tomcat加載結(jié)構(gòu)圖如下:

圖片圖片

JDBC 是如何打破雙親委派模型的?

實際上JDBC定義了接口,具體的實現(xiàn)類是由各個廠商進行實現(xiàn)的(比如MySQL)

類加載有個規(guī)則:如果一個類由類加載器A加載,那么這個類的依賴類也是由「相同的類加載器」加載。

而在用JDBC的時候,是使用DriverManager獲取Connection的,DriverManager是在java.sql包下的,顯然是由BootStrap類加載器進行裝載的。當使用DriverManager.getConnection ()時,需要得到的一定是對應(yīng)廠商(如Mysql)實現(xiàn)的類。這里在去獲取Connection的時候,是使用「線程上下文加載器」去加載Connection的,線程上下文加載器會直接指定對應(yīng)的加載器去加載。也就是說,在BootStrap類加載器利用「線程上下文加載器」指定了對應(yīng)的類的加載器去加載

圖片圖片

什么線程上下文加載器?

Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實現(xiàn)。常見的 SPI 有 JDBC 。

這些 SPI 的接口由 Java 核心庫來提供,而這些 SPI 的實現(xiàn)代碼則是作為 Java 應(yīng)用所依賴的 jar 包被包含進類路徑(CLASSPATH)里。SPI接口中的代碼經(jīng)常需要加載具體的實現(xiàn)類。那么問題來了,SPI的接口是Java核心庫的一部分,是由啟動類加載器來加載的;SPI的實現(xiàn)類是由系統(tǒng)類加載器來加載的。啟動類加載器是無法找到 SPI 的實現(xiàn)類的,因為它只加載 Java 的核心庫。它也不能委派給系統(tǒng)類加載器,因為它是系統(tǒng)類加載器的祖先類加載器。

線程上下文類加載器正好解決了這個問題。如果不做任何的設(shè)置,Java 應(yīng)用的線程的上下文類加載器默認就是系統(tǒng)上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就可以成功的加載到 SPI 實現(xiàn)的類。線程上下文類加載器在很多 SPI 的實現(xiàn)中都會用到。

線程上下文加載器的一般使用模式(獲取 - 使用 - 還原)。

ClassLoader calssLoader = Thread.currentThread().getContextClassLoader();
 
try {
    //設(shè)置線程上下文類加載器為自定義的加載器
    Thread.currentThread.setContextClassLoader(targetTccl);
    myMethod(); //執(zhí)行自定義的方法
} finally {
    //還原線程上下文類加載器
    Thread.currentThread().setContextClassLoader(classLoader);
}

能自定義類加載器加載 java.lang.String嗎?

很多人都有個誤區(qū):雙親委派機制不能被打破,不能使用自定義類加載器加載java.lang.String

但是事實上并不是,只要重寫ClassLoader的loadClass()方法,就能打破了。

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

publicclass MyClassLoader extends URLClassLoader {

    public MyClassLoader(URL[] urls) {
        super(urls);
    }
    
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        //只對MyClassLoader和String使用自定義的加載,其他的還是走雙親委派
        if(name.equals("MyClassLoader") || name.equals("java.lang.String")) {
            returnsuper.findClass(name);
        } else {
            return getParent().loadClass(name);
        }
    }

    public static void main(String[] args) throws Exception {
        //urls指定自定義類加載器的加載路徑
        URL url = new File("J:/apps/demo/target/classes/").toURI().toURL();
        URL url3 = new File("C:/Program Files/Java/jdk1.8.0_191/jre/lib/rt.jar").toURI().toURL();
        URL[] urls = {
                url
                , url3
        };
        MyClassLoader myClassLoader = new MyClassLoader(urls);

        Class<?> c1 = MyClassLoader.class.getClassLoader().loadClass("MyClassLoader");
        Class<?> c2 = myClassLoader.loadClass("MyClassLoader");
        System.out.println(c1 == c2); //false
        System.out.println(c1.getClassLoader()); //AppClassLoader
        System.out.println(c2.getClassLoader()); //MyClassLoader

        System.out.println(myClassLoader.loadClass("java.lang.String")); //Exception 
    }

}

加載同一個類MyClassLoader,使用的類加載器不同,說明這里是打破了雙親委派機制的,但是嘗試加載String類的時候報錯了

圖片圖片

看代碼是ClassLoader類里面的限制,只要加載java開頭的包就會報錯。所以真正原因是JVM安全機制,并不是因為雙親委派。

那么既然是ClassLoader里面的代碼做的限制,那把ClassLoader.class修改了不就好了嗎。

寫了個java.lang.ClassLoader,把preDefineClass()方法里那段if直接刪掉,再用編譯后的class替換rt.jar里面的,直接通過命令jar uvf rt.jar java/lang/ClassLoader/class即可。

不過事與愿違,修改之后還是報錯:

Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
    at com.example.demo.mini.test.MyClassLoader.loadClass(MyClassLoader.java:17)
    at com.example.demo.mini.test.MyClassLoader.main(MyClassLoader.java:31)

仔細看報錯和之前的不一樣了,這次是native方法報錯了。這就比較難整了,看來要自己重新編譯個JVM才行了。理論上來說,編譯JVM的時候把校驗的代碼去掉就行了。

結(jié)論:自定義類加載器加載java.lang.String,必須修改jdk的源碼,自己重新編譯個JVM才行。

責任編輯:武曉燕 來源: SevenCoding
相關(guān)推薦

2023-12-06 12:11:43

類加載器雙親委派模型

2024-12-04 09:01:55

引導類加載器C++

2023-05-10 11:07:18

2024-03-27 09:15:27

2024-03-12 07:44:53

JVM雙親委托機制類加載器

2021-07-05 06:51:43

Java機制類加載器

2025-06-26 03:33:00

2024-06-24 08:24:57

2023-10-31 16:00:51

類加載機制Java

2024-09-06 09:37:45

WebApp類加載器Web 應(yīng)用

2023-10-30 01:02:56

Java類類加載器雙親委派

2024-12-02 09:01:23

Java虛擬機內(nèi)存

2021-01-06 09:01:05

javaclass

2021-03-01 08:54:39

開發(fā)雙親委派

2017-09-20 08:07:32

java加載機制

2017-03-08 10:30:43

JVMJava加載機制

2009-02-03 09:42:53

JAVA類JVM指令forName方法

2020-10-26 11:20:04

jvm類加載Java

2020-05-20 22:13:26

JVM加載機制虛擬機

2024-09-04 09:47:21

點贊
收藏

51CTO技術(shù)棧公眾號