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

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

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

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

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

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

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

JVM類加載機(jī)制?

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

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

什么是雙親委派機(jī)制?

一個(gè)類加載器收到一個(gè)類的加載請(qǐng)求時(shí),它首先不會(huì)自己嘗試去加載它,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,這樣層層委派,因此所有的加載請(qǐng)求最終都會(huì)傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父類加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。

圖片圖片

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

雙親委派模型目的?

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

什么時(shí)候需要打破雙親委派模型?

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

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

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

有兩種方式:

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

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

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

什么是依賴傳遞原則?

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

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

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

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

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

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

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

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

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

圖片圖片

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

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

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

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

圖片圖片

什么線程上下文加載器?

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

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

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

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

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

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

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

但是事實(shí)上并不是,只要重寫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 {
        //只對(duì)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 
    }

}

加載同一個(gè)類MyClassLoader,使用的類加載器不同,說明這里是打破了雙親委派機(jī)制的,但是嘗試加載String類的時(shí)候報(bào)錯(cuò)了

圖片圖片

看代碼是ClassLoader類里面的限制,只要加載java開頭的包就會(huì)報(bào)錯(cuò)。所以真正原因是JVM安全機(jī)制,并不是因?yàn)殡p親委派。

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

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

不過事與愿違,修改之后還是報(bào)錯(cuò):

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)

仔細(xì)看報(bào)錯(cuò)和之前的不一樣了,這次是native方法報(bào)錯(cuò)了。這就比較難整了,看來要自己重新編譯個(gè)JVM才行了。理論上來說,編譯JVM的時(shí)候把校驗(yàn)的代碼去掉就行了。

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

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

2023-12-06 12:11:43

類加載器雙親委派模型

2024-12-04 09:01:55

引導(dǎo)類加載器C++

2023-05-10 11:07:18

2024-03-27 09:15:27

2024-03-12 07:44:53

JVM雙親委托機(jī)制類加載器

2021-07-05 06:51:43

Java機(jī)制類加載器

2025-06-26 03:33:00

2023-10-31 16:00:51

類加載機(jī)制Java

2024-12-02 09:01:23

Java虛擬機(jī)內(nèi)存

2024-09-06 09:37:45

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

2021-01-06 09:01:05

javaclass

2024-06-24 08:24:57

2023-10-30 01:02:56

Java類類加載器雙親委派

2017-09-20 08:07:32

java加載機(jī)制

2021-03-01 08:54:39

開發(fā)雙親委派

2017-03-08 10:30:43

JVMJava加載機(jī)制

2009-02-03 09:42:53

JAVA類JVM指令forName方法

2020-10-26 11:20:04

jvm類加載Java

2020-05-20 22:13:26

JVM加載機(jī)制虛擬機(jī)

2024-09-04 09:47:21

點(diǎn)贊
收藏

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