成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

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

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

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

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

JVM類加載機制?

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

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

什么是雙親委派機制?

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

圖片圖片

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

雙親委派模型目的?

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

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

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

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

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

有兩種方式:

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

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

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

什么是依賴傳遞原則?

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

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

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

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

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

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

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

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

Tomcat加載結構圖如下:

圖片圖片

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

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

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

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

圖片圖片

什么線程上下文加載器?

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

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

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

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

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

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

很多人都有個誤區:雙親委派機制不能被打破,不能使用自定義類加載器加載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的時候把校驗的代碼去掉就行了。

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

責任編輯:武曉燕 來源: SevenCoding
相關推薦

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 應用

2023-10-30 01:02:56

Java類類加載器雙親委派

2024-12-02 09:01:23

Java虛擬機內存

2021-01-06 09:01:05

javaclass

2021-03-01 08:54:39

開發雙親委派

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技術棧公眾號

主站蜘蛛池模板: 日韩久久精品视频 | 97在线播放| 成人免费看片又大又黄 | 国产成人精品福利 | 91操操操 | 亚州中文 | 欧美精品一区二区三区在线 | 国产一区精品 | 国产99久久精品一区二区永久免费 | 日本三级电影在线观看视频 | 在线日韩精品视频 | 日韩成人免费中文字幕 | 成人福利在线 | 日韩国产在线观看 | 黄色国产 | 色先锋影音| 久久亚洲一区二区三区四区 | 综合久久综合久久 | 中文字幕亚洲一区二区三区 | 国产极品粉嫩美女呻吟在线看人 | 国产99久久久国产精品下药 | 懂色av一区二区三区在线播放 | jizz在线看片 | 国产激情一区二区三区 | 国产免费让你躁在线视频 | 日韩免费一区二区 | 一区二区精品 | 国产精品美女久久久 | 国产欧美视频一区 | 91视频正在播放 | 一级毛片免费看 | 天堂男人av | 亚洲精品自在在线观看 | 青娱乐国产 | 欧美视频免费在线观看 | 色天天综合 | 日日夜夜精品视频 | 午夜精品一区二区三区在线观看 | 日韩精品久久一区二区三区 | 国产精品一区二区不卡 | 国产日韩欧美一区二区 |