日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

一文理类加载相关知识:类加载器、双亲委派、SPI

發(fā)布時(shí)間:2023/12/3 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文理类加载相关知识:类加载器、双亲委派、SPI 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

思維導(dǎo)圖

類加載的時(shí)機(jī)

類加載的流程

類從被加載到內(nèi)存中開(kāi)始,直到被從內(nèi)存中卸載為止,它的整個(gè)生命周期包括:驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載7 個(gè)階段。
其中驗(yàn)證、準(zhǔn)備、解析 3 個(gè)部分統(tǒng)稱為連接(Linking)

1.加載(重點(diǎn))

類加載過(guò)程的第一步,主要完成下面 3 件事情:

  • 通過(guò)全類名獲取定義此類的二進(jìn)制字節(jié)流
  • 將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
  • 在內(nèi)存中生成一個(gè)代表該類的 Class 對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問(wèn)入口
  • 加載階段(準(zhǔn)確地說(shuō),是加載階段中獲取類的二進(jìn)制字節(jié)流的動(dòng)作)可以使用系統(tǒng)提供的類加載器(ClassLoader)來(lái)完成,也可以由用戶自定義的類加載器完成,開(kāi)發(fā)人員可以通過(guò)定義自己的類加載器去控制字節(jié)流的獲取方式。

    加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,方法區(qū)中的數(shù)據(jù)存儲(chǔ)格式由虛擬機(jī)實(shí)現(xiàn)自行定義,虛擬機(jī)并未規(guī)定此區(qū)域的具體數(shù)據(jù)結(jié)構(gòu)。然后在java堆中實(shí)例化一個(gè)java.lang.Class類的對(duì)象,這個(gè)對(duì)象作為程序訪問(wèn)方法區(qū)中的這些類型數(shù)據(jù)的外部接口。

    2.驗(yàn)證

    驗(yàn)證是鏈接階段的第一步,這一步主要的目的是確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身安全。
    驗(yàn)證階段主要包括四個(gè)檢驗(yàn)過(guò)程:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證。

    3.準(zhǔn)備

    準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。這個(gè)階段中有兩個(gè)容易產(chǎn)生混淆的知識(shí)點(diǎn),首先是這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static 修飾的變量),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在java堆中

    4.解析

    解析階段是虛擬機(jī)常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。

    • 符號(hào)引用:符號(hào)引用是一組符號(hào)來(lái)描述所引用的目標(biāo)對(duì)象,符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)對(duì)象并不一定已經(jīng)加載到內(nèi)存中。
    • 直接引用:直接引用可以是直接指向目標(biāo)對(duì)象的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是與虛擬機(jī)內(nèi)存布局實(shí)現(xiàn)相關(guān)的,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。

    5.初始化

    類的初始化階段是類加載過(guò)程的最后一步,在準(zhǔn)備階段,類變量已賦過(guò)一次系統(tǒng)要求的初始值,而在初始化階段,則是根據(jù)程序員通過(guò)程序制定的主觀計(jì)劃去初始化類變量和其他資源,或者可以從另外一個(gè)角度來(lái)表達(dá):初始化階段是執(zhí)行類構(gòu)造器()方法的過(guò)程。

    類加載器

    類加載器主要分為四類:

    BootStrap ClassLoader:啟動(dòng)類加載器,C++實(shí)現(xiàn)的,是Java類加載層次中最頂層的類加載器(JVM啟動(dòng)后初始化的),負(fù)責(zé)加載JDK中的核心類庫(kù),如:rt.jar、resources.jar、charsets.jar等;

    ExtensionClassLoader:擴(kuò)展類加載器,負(fù)責(zé)加載Java的擴(kuò)展類庫(kù),默認(rèn)加載JAVA_HOME/jre/lib/ext/目下的所有jar。該加載器是有java實(shí)現(xiàn)的,由Bootstrploader加載ExtClassLoader,并且將ExtClassLoader的父加載器設(shè)置為Bootstrp loader;

    AppClassLoader:系統(tǒng)類加載器,負(fù)責(zé)加載應(yīng)用程序classpath目錄下的所有jar和class文件。

    CustomLoader:自定義類加載器,負(fù)責(zé)加載指定的目錄和文件

    雙親委派

    類加載器在加載類時(shí),會(huì)先委托父類加載器去加載該類,如果父類加載器無(wú)法加載才會(huì)嘗試自己加載。

    當(dāng)一個(gè)ClassLoader實(shí)例需要加載某個(gè)類時(shí),它會(huì)先檢查父類加載器(一直檢查到Bootstrap ClassLoader)是否已經(jīng)加載過(guò)該類,如果父類加載器已經(jīng)加載該類則直接返回該類對(duì)象。然后由上至下依次加載類,首先由最頂層的類加載器BootstrapClassLoader試圖加載,如果沒(méi)加載到,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載,如果也沒(méi)加載到,則轉(zhuǎn)交給AppClassLoader進(jìn)行加載,如果它也沒(méi)有加載得到的話,則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網(wǎng)絡(luò)等URL中加載該類。如果它們都沒(méi)有加載到這個(gè)類時(shí),則拋出ClassNotFoundException異常。

    JVM在判定兩個(gè)Class是否相同時(shí),不僅要判斷兩個(gè)類名是否相同,而且要判斷是否由同一個(gè)類加載器實(shí)例加載的。只有兩者同時(shí)滿足的情況下,JVM才認(rèn)為這兩個(gè)class是相同的。

    雙親委派的優(yōu)點(diǎn)

  • 可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類的時(shí)候,就沒(méi)有必要讓子ClassLoader再加載一次。
  • 避免Java核心類不被隨意替換
  • 打破雙親委派

    在實(shí)際的應(yīng)用中雙親委派解決了java 基礎(chǔ)類統(tǒng)一加載的問(wèn)題,但是也存在著問(wèn)題。jdk中的基礎(chǔ)類作為用戶api被調(diào)用,但是也存在調(diào)用用戶的代碼的情況,典型的如SPI。

    Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見(jiàn)的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
    這些 SPI 的接口由 Java 核心庫(kù)來(lái)提供,而這些 SPI 的實(shí)現(xiàn)代碼則是作為 Java 應(yīng)用所依賴的 jar 包被包含進(jìn)類路徑(CLASSPATH)里。SPI接口中的代碼經(jīng)常需要加載具體的實(shí)現(xiàn)類。

    那么問(wèn)題來(lái)了,SPI的接口是Java核心庫(kù)的一部分,是由啟動(dòng)類加載器(Bootstrap Classloader)加載的,而SPI的實(shí)現(xiàn)類是由系統(tǒng)類加載器(App ClassLoader)來(lái)加載的。啟動(dòng)類加載器是無(wú)法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)橐勒针p親委派模型,BootstrapClassloader無(wú)法委派AppClassLoader來(lái)加載類

    (1)線程上下文類加載器

    為解決上述問(wèn)題,引入了線程上下文類加載器(Thread Context ClassLoader),線程上下文類加載器可以通過(guò)java.lang.Thread 類的setContextClassLoader方法進(jìn)行設(shè)置。默認(rèn)情況下為系統(tǒng)類加載器(App ClassLoader)
    通過(guò)線程上下文類加載器,父類即可打破雙親委派模型,委托子類加載器實(shí)現(xiàn)類的加載。當(dāng)父類無(wú)法加載某個(gè)類時(shí),就可以委托線程上下文類加載器加載對(duì)應(yīng)的類。

    (2)自定義類加載器覆寫(xiě)loadClass()

    自定義加載器,需要繼承 ClassLoader 。如果不想打破雙親委派模型,就重寫(xiě) ClassLoader 類中的 findClass() 方法,無(wú)法被父類加載器加載的類最終會(huì)通過(guò)這個(gè)方法被加載。但是,如果想打破雙親委派模型則需要重寫(xiě) loadClass() 方法。

    SPI

    SPI(服務(wù)提供接口) ,全稱為 Service Provider Interface,可以理解為調(diào)用方來(lái)制定接口規(guī)范,提供給外部來(lái)實(shí)現(xiàn),調(diào)用方在調(diào)用時(shí)則選擇自己需要的外部實(shí)現(xiàn)。SPI接口一般在核心庫(kù)里,由BootStrap ClassLoader加載。

    SPI是一種服務(wù)發(fā)現(xiàn)機(jī)制。SPI約定在 Classpath 下的 META-INF/services/ 目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件(如:java.sql.Driver),然后文件里面記錄的是此 jar 包提供的接口具體實(shí)現(xiàn)類的全限定名(如:Mysql中提供的 com.mysql.cj.jdbc.Driver)。
    在加載接口的實(shí)現(xiàn)類時(shí),通過(guò)在查找ClassPath路徑下的META-INF/services文件夾中存有實(shí)現(xiàn)類類名的文件,并實(shí)例化文件所定義的實(shí)現(xiàn)類,來(lái)實(shí)例化某個(gè)接口。

    SPI 通過(guò) ServiceLoader.load() 去完成上述的實(shí)例化META-INF/services中的類。ServiceLoader.load() 會(huì)通過(guò) 線程上下文類加載器(默認(rèn)為App Loader)打破雙親委派,委子類類加載器去加載實(shí)現(xiàn)類。

    SPI的主要流程:約定一個(gè)目錄,調(diào)用ServiceLoader.load()根據(jù)接口名去那個(gè)目錄找到文件,文件解析得到實(shí)現(xiàn)類的全限定名,然后循環(huán)加載實(shí)現(xiàn)類和創(chuàng)建其實(shí)例。

    圖片來(lái)源

    Java SPI的缺點(diǎn)

    Java SPI 無(wú)法按需加載實(shí)現(xiàn)類:Java SPI 在查找擴(kuò)展實(shí)現(xiàn)類的時(shí)候遍歷 SPI 的配置文件并且將實(shí)現(xiàn)類全部實(shí)例化,假設(shè)一個(gè)實(shí)現(xiàn)類初始化過(guò)程比較消耗資源且耗時(shí),但是你的代碼里面又用不上它,這就產(chǎn)生了資源的浪費(fèi)。

    推薦閱讀:

    • 推薦:三歪問(wèn)我Dubbo的SPI機(jī)制是啥?(帶有ServiceLoader的源碼分析)
    • Java SPI詳解
    • 推薦:深入理解SPI機(jī)制

    總結(jié)

    類的加載過(guò)程基本如下圖:

  • 大部分類都依賴雙親委派模型進(jìn)行加載;
  • 以下情況會(huì)破壞雙親委派模型:
    (1)自定義類加載器覆寫(xiě)了loadClass()方法
    (2)父類加載器需要使用由子類加載器加載的類,此時(shí)父類加載器會(huì)使用線程上下文加載器,去委托子類加載器去加載相應(yīng)的類
  • 線程上下文類加載器的適用場(chǎng)景:
    (1)當(dāng)高層提供了統(tǒng)一接口讓低層去實(shí)現(xiàn),同時(shí)又要是在高層加載(或?qū)嵗?#xff09;低層的類時(shí),必須通過(guò)線程上下文類加載器來(lái)幫助高層的ClassLoader找到并加載該類。
    (2)當(dāng)使用本類托管類加載,然而加載本類的ClassLoader未知時(shí),為了隔離不同的調(diào)用者,可以取調(diào)用者各自的線程上下文類加載器代為托管。
  • 參考

    • 推薦:【JVM】淺談雙親委派和破壞雙親委派
    • 推薦:詳細(xì)jvm-類加載機(jī)制
    • 推薦:Java 類加載器
    • 推薦:深入理解SPI機(jī)制
    • 真正理解線程上下文類加載器(多案例分析)
    • 自定義類加載器:從網(wǎng)上加載class到內(nèi)存、實(shí)例化調(diào)用其中的方法
    • jvm(1)類的加載(三)(線程上下文加載器)
    • 類加載過(guò)程

    總結(jié)

    以上是生活随笔為你收集整理的一文理类加载相关知识:类加载器、双亲委派、SPI的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。