国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

類的加載機(jī)制,雙親委派模型,搞定大廠高頻面試題

Object / 3523人閱讀

摘要:驗(yàn)證驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。字節(jié)碼驗(yàn)證通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的符合邏輯的。

看過這篇文章,大廠面試你「雙親委派模型」,硬氣的說一句,你怕啥?

讀該文章姿勢(shì)

打開手頭的 IDE,按照文章內(nèi)容及思路進(jìn)行代碼跟蹤與思考

手頭沒有 IDE,先收藏,回頭看 (萬一哪次面試問了呢)

需要查看和拷貝代碼,點(diǎn)擊文章末尾出「閱讀原文」

文章內(nèi)容相對(duì)較長(zhǎng),所以添加了目錄,如果你希望對(duì) Java 的類加載過程有個(gè)更深入的了解,同時(shí)增加自己的面試技能點(diǎn),請(qǐng)耐心讀完......

雙親委派模型

在介紹這個(gè)Java技術(shù)點(diǎn)之前,先試著思考以下幾個(gè)問題:

為什么我們不能定義同名的 String 的 java 文件?

多線程的情況下,類的加載為什么不會(huì)出現(xiàn)重復(fù)加載的情況?

熱部署的原理是什么?

下面代碼,虛擬機(jī)是怎樣初始化注冊(cè) Mysql 連接驅(qū)動(dòng)(Driver)的?


想理解以上幾個(gè)問題的前提是了解類加載時(shí)機(jī)與過程, 這篇文章將會(huì)以非常詳細(xì)的解讀方式來回答以上幾個(gè)問題

類加載時(shí)機(jī)與過程

類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個(gè)階段。其中準(zhǔn)備、驗(yàn)證、解析3個(gè)部分統(tǒng)稱為連接(Linking)。如圖所示

加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這5個(gè)階段的順序是確定的,類的加載過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開始,這是為了支持Java語言的運(yùn)行時(shí)綁定(也稱為動(dòng)態(tài)綁定或晚期綁定)

加載

在加載階段(可以參考java.lang.ClassLoader的loadClass()方法),虛擬機(jī)需要完成以下3件事情:

通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流(并沒有指明要從一個(gè)Class文件中獲取,可以從其他渠道,譬如:網(wǎng)絡(luò)、動(dòng)態(tài)生成、數(shù)據(jù)庫等);

將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);

在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口;

加載階段和連接階段(Linking)的部分內(nèi)容(如一部分字節(jié)碼文件格式驗(yàn)證動(dòng)作)是交叉進(jìn)行的,加載階段尚未完成,連接階段可能已經(jīng)開始,但這些夾在加載階段之中進(jìn)行的動(dòng)作,仍然屬于連接階段的內(nèi)容,這兩個(gè)階段的開始時(shí)間仍然保持著固定的先后順序。

驗(yàn)證

驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
驗(yàn)證階段大致會(huì)完成4個(gè)階段的檢驗(yàn)動(dòng)作:

文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范;例如:是否以魔術(shù)0xCAFEBABE開頭(當(dāng)class文件以二進(jìn)制形式打開,會(huì)看到這個(gè)文件頭,cafebabe)、主次版本號(hào)是否在當(dāng)前虛擬機(jī)的處理范圍之內(nèi)、常量池中的常量是否有不被支持的類型。

元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語義分析(注意:對(duì)比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規(guī)范的要求;例如:這個(gè)類是否有父類,除了java.lang.Object之外。

字節(jié)碼驗(yàn)證:通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的、符合邏輯的。

符號(hào)引用驗(yàn)證:確保解析動(dòng)作能正確執(zhí)行。

驗(yàn)證階段是非常重要的,但不是必須的,它對(duì)程序運(yùn)行期沒有影響,如果所引用的類經(jīng)過反復(fù)驗(yàn)證,那么可以考慮采用-Xverifynone參數(shù)來關(guān)閉大部分的類驗(yàn)證措施,以縮短虛擬機(jī)類加載的時(shí)間。

準(zhǔn)備

準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在堆中。其次,這里所說的初始值通常情況下是數(shù)據(jù)類型的零值,假設(shè)一個(gè)類變量的定義為:

通常情況就有特殊情況,這里的特殊是指:

解析

解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程。解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用進(jìn)行。

初始化

在介紹初始化時(shí),要先介紹兩個(gè)方法: :

在編譯生成class文件時(shí),會(huì)自動(dòng)產(chǎn)生兩個(gè)方法,一個(gè)是類的初始化方法, 另一個(gè)是實(shí)例的初始化方法

clinit>:在jvm第一次加載class文件時(shí)調(diào)用,包括靜態(tài)變量初始化語句和靜態(tài)塊的執(zhí)行

: 在實(shí)例創(chuàng)建出來的時(shí)候調(diào)用,包括調(diào)用new操作符;調(diào)用 Class 或 Java.lang.reflect.Constructor 對(duì)象的newInstance()方法;調(diào)用任何現(xiàn)有對(duì)象的clone()方法;通過 java.io.ObjectInputStream 類的getObject() 方法反序列化。

類初始化階段是類加載過程的最后一步,到了初始化階段,才真正開始執(zhí)行類中定義的java程序代碼。在準(zhǔn)備極端,變量已經(jīng)付過一次系統(tǒng)要求的初始值,而在初始化階段,則根據(jù)程序猿通過程序制定的主管計(jì)劃去初始化類變量和其他資源,或者說:初始化階段是執(zhí)行類構(gòu)造器()方法的過程.

()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語句塊 static{} 中的語句合并產(chǎn)生的,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語句塊只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語句塊可以賦值,但是不能訪問。如下:

那么去掉報(bào)錯(cuò)的那句,改成下面:

輸出結(jié)果:1

為什么輸出結(jié)果是 1,在準(zhǔn)備階段我們知道 i=0,然后類初始化階段按照順序執(zhí)行,首先執(zhí)行 static 塊中的 i=0,接著執(zhí)行 static賦值操作i=1, 最后在 main 方法中獲取 i 的值為1

()方法與實(shí)例構(gòu)造器()方法不同,它不需要顯示地調(diào)用父類構(gòu)造器,虛擬機(jī)會(huì)保證在子類()方法執(zhí)行之前,父類的()方法方法已經(jīng)執(zhí)行完畢

由于父類的()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作。

()方法對(duì)于類或者接口來說并不是必需的,如果一個(gè)類中沒有靜態(tài)語句塊,也沒有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類生產(chǎn)()方法。

接口中不能使用靜態(tài)語句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會(huì)生成()方法。但接口與類不同的是,執(zhí)行接口的()方法不需要先執(zhí)行父接口的()方法。只有當(dāng)父接口中定義的變量使用時(shí),父接口才會(huì)初始化。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的()方法。

虛擬機(jī)會(huì)保證一個(gè)類的()方法在多線程環(huán)境中被正確的加鎖、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的()方法,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行()方法完畢。如果在一個(gè)類的()方法中有耗時(shí)很長(zhǎng)的操作,就可能造成多個(gè)線程阻塞,在實(shí)際應(yīng)用中這種阻塞往往是隱藏的。

讓我們來驗(yàn)證上面的加載規(guī)則

驗(yàn)證 1: 虛擬機(jī)會(huì)保證在子類()方法執(zhí)行之前,父類的()方法方法已經(jīng)執(zhí)行完畢

輸出結(jié)果
SSClass
SuperClass init!
123
驗(yàn)證 2: 通過數(shù)組定義來引用類,不會(huì)觸發(fā)此類的初始化(我的理解是數(shù)組的父類是Object)

輸出結(jié)果:無

驗(yàn)證 3: 常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化

輸出結(jié)果:
hello world
驗(yàn)證小結(jié)

虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有5中情況(jdk1.7)必須對(duì)類進(jìn)行“初始化”(而加載、驗(yàn)證、準(zhǔn)備自然需要在此之前開始):

遇到 new, getstatic, putstatic, invokestatic 這些字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。生成這4條指令的最常見的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。

使用 java.lang.reflect 包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。

當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。

當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。

當(dāng)使用jdk1.7動(dòng)態(tài)語言支持時(shí),如果一個(gè) java.lang.invoke.MethodHandle 實(shí)例最后的解析結(jié)果REF_getstatic, REF_putstatic, REF_invokeStatic 的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行初始化,則需要先出觸發(fā)其初始化。

有了這個(gè)加載規(guī)則的印象,雙親委派模型就很好理解了,別著急,繼續(xù)向下看, 你會(huì)發(fā)現(xiàn)你的理解層面提高了

雙親委派模型

剛看到這個(gè)詞匯的時(shí)候我是完全懵懂的狀態(tài),其實(shí)就是定義了 JVM 啟動(dòng)的時(shí)候類的加載規(guī)則, 大家要按規(guī)矩辦事,好辦事,來看下圖:

所謂雙親委派是指每次收到類加載請(qǐng)求時(shí),先將請(qǐng)求委派給父類加載器完成(所有加載請(qǐng)求最終會(huì)委派到頂層的Bootstrap ClassLoader加載器中),如果父類加載器無法完成這個(gè)加載(該加載器的搜索范圍中沒有找到對(duì)應(yīng)的類),子類嘗試自己加載, 如果都沒加載到,則會(huì)拋出 ClassNotFoundException 異常, 看到這里其實(shí)就解釋了文章開頭提出的第一個(gè)問題,父加載器已經(jīng)加載了JDK 中的 String.class 文件,所以我們不能定義同名的 String java 文件。

為什么會(huì)有這樣的規(guī)矩設(shè)定?

因?yàn)檫@樣可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類的時(shí)候,就沒有必要 ClassLoader 再加載一次。考慮到安全因素,我們?cè)囅胍幌拢绻皇褂眠@種委托模式,那我們就可以隨時(shí)使用自定義的String來動(dòng)態(tài)替代java核心api中定義的類型,這樣會(huì)存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因?yàn)镾tring 已經(jīng)在啟動(dòng)時(shí)就被引導(dǎo)類加載器(Bootstrcp ClassLoader)加載,所以用戶自定義的ClassLoader永遠(yuǎn)也無法加載一個(gè)自己寫的String,除非你改變 JDK 中 ClassLoader 搜索類的默認(rèn)算法。

我們發(fā)現(xiàn)除了啟動(dòng)類加載器(BootStrap ClassLoader),每個(gè)類都有其"父類"加載器

?? 其實(shí)這里的父子關(guān)系是組合模式,不是繼承關(guān)系來實(shí)現(xiàn)

從圖中可以看到類 AppClassLoaderExtClassLoader 都繼承 URLClassLoader, 而 URLClassLoader 又繼承 ClassLoader, 在 ClassLoader 中有一個(gè)屬性

在通過構(gòu)造函數(shù)實(shí)例化 AppClassLoaderExtClassLoader 的時(shí)候都要傳入一個(gè) classloader 作為當(dāng)前 classloader 的 parent

頂層ClassLoader有幾個(gè)函數(shù)很關(guān)鍵,先有個(gè)印象

指定保護(hù)域(protectionDomain),把ByteBuffer的內(nèi)容轉(zhuǎn)換成 Java 類,這個(gè)方法被聲明為final的

把字節(jié)數(shù)組 b中的內(nèi)容轉(zhuǎn)換成 Java 類,其開始偏移為off,這個(gè)方法被聲明為final的

查找指定名稱的類

鏈接指定的類

類加載器責(zé)任范圍

上面我們提到每個(gè)加載器都有對(duì)應(yīng)的加載搜索范圍

Bootstrap ClassLoader:這個(gè)加載器不是一個(gè)Java類,而是由底層的c++實(shí)現(xiàn),負(fù)責(zé)在虛擬機(jī)啟動(dòng)時(shí)加載Jdk核心類庫(如:rt.jar、resources.jar、charsets.jar等)以及加載后兩個(gè)類加載器。這個(gè)ClassLoader完全是JVM自己控制的,需要加載哪個(gè)類,怎么加載都是由JVM自己控制,別人也訪問不到這個(gè)類

Extension ClassLoader:是一個(gè)普通的Java類,繼承自ClassLoader類,負(fù)責(zé)加載{JAVA_HOME}/jre/lib/ext/目錄下的所有jar包。

App ClassLoader:是Extension ClassLoader的子對(duì)象,負(fù)責(zé)加載應(yīng)用程序classpath目錄下的所有jar和class文件。

大家自行運(yùn)行這個(gè)文件,就可以看到每個(gè)類加載器加載的文件了

兩種類的加載方式

通常用這兩種方式來動(dòng)態(tài)加載一個(gè) java 類,Class.forName()ClassLoader.loadClass() 但是兩個(gè)方法之間也是有一些細(xì)微的差別

Class.forName() 方式

查看Class類的具體實(shí)現(xiàn)可知,實(shí)質(zhì)上這個(gè)方法是調(diào)用原生的方法:

形式上類似于Class.forName(name,true,currentLoader)。 綜上所述,Class.forName 如果調(diào)用成功會(huì):

保證一個(gè)Java類被有效得加載到內(nèi)存中;

類默認(rèn)會(huì)被初始化,即執(zhí)行內(nèi)部的靜態(tài)塊代碼以及保證靜態(tài)屬性被初始化;

默認(rèn)會(huì)使用當(dāng)前的類加載器來加載對(duì)應(yīng)的類

ClassLoader.loadClass方式

如果采用這種方式的類加載策略,由于雙親托管模型的存在,最終都會(huì)將類的加載任務(wù)交付給Bootstrap ClassLoader進(jìn)行加載。跟蹤源代碼,最終會(huì)調(diào)用原生方法:

與此同時(shí),與上一種方式的最本質(zhì)的不同是,類不會(huì)被初始化,只有顯式調(diào)用才會(huì)進(jìn)行初始化。綜上所述,ClassLoader.loadClass 如果調(diào)用成功會(huì):

類會(huì)被加載到內(nèi)存中;

類不會(huì)被初始化,只有在之后被第一次調(diào)用時(shí)類才會(huì)被初始化;

之所以采用這種方式的類加載,是提供一種靈活度,可以根據(jù)自身的需求繼承ClassLoader類實(shí)現(xiàn)一個(gè)自定義的類加載器實(shí)現(xiàn)類的加載。(很多開源Web項(xiàng)目中都有這種情況,比如tomcat,struct2,jboss。原因是根據(jù)Java Servlet規(guī)范的要求,既要Web應(yīng)用自己的類的優(yōu)先級(jí)要高于Web容器提供的類,但同時(shí)又要保證Java的核心類不被任意覆蓋,此時(shí)重寫一個(gè)類加載器就很必要了)

雙親委派模型源碼分析 Launcher

分析類加載器源碼要從 sun.misc.Launcher.class 文件看起, 關(guān)鍵代碼已添加注釋,同時(shí)可以在此類中看到 ExtClassLoader 和 AppClassLoader 的定義,也驗(yàn)證了我們上文提到的他們不是繼承關(guān)系,而是通過指定 parent 屬性來形成的組合模型

進(jìn)入上面第25行的 loadClass 方法中

我們看到方法有同步塊(synchronized), 這也就解釋了文章開頭第2個(gè)問題,多線程情況不會(huì)出現(xiàn)重復(fù)加載的情況。同時(shí)會(huì)詢問parent classloader是否有加載,如果沒有,自己嘗試加載。

URLClassLoader中的 findClass方法:

借用網(wǎng)友的一個(gè)加載時(shí)序圖來解釋整個(gè)過程更加清晰:

雙親委派模型的破壞

Java本身有一套資源管理服務(wù)JNDI,是放置在rt.jar中,由啟動(dòng)類加載器加載的。以對(duì)數(shù)據(jù)庫管理JDBC為例,java給數(shù)據(jù)庫操作提供了一個(gè)Driver接口:

然后提供了一個(gè)DriverManager來管理這些Driver的具體實(shí)現(xiàn):

這里省略了大部分代碼,可以看到我們使用數(shù)據(jù)庫驅(qū)動(dòng)前必須先要在DriverManager中使用registerDriver()注冊(cè),然后我們才能正常使用。

不破壞雙親委派模型的情況(不使用JNDI服務(wù))

我們看下mysql的驅(qū)動(dòng)是如何被加載的:

核心就是這句Class.forName()觸發(fā)了mysql驅(qū)動(dòng)的加載,我們看下mysql對(duì)Driver接口的實(shí)現(xiàn):

可以看到,Class.forName()其實(shí)觸發(fā)了靜態(tài)代碼塊,然后向DriverManager中注冊(cè)了一個(gè)mysql的Driver實(shí)現(xiàn)。這個(gè)時(shí)候,我們通過DriverManager去獲取connection的時(shí)候只要遍歷當(dāng)前所有Driver實(shí)現(xiàn),然后選擇一個(gè)建立連接就可以了。

破壞雙親委派模型的情況

在JDBC4.0以后,開始支持使用spi的方式來注冊(cè)這個(gè)Driver,具體做法就是在mysql的jar包中的META-INF/services/java.sql.Driver 文件中指明當(dāng)前使用的Driver是哪個(gè),然后使用的時(shí)候就直接這樣就可以了:

可以看到這里直接獲取連接,省去了上面的Class.forName()注冊(cè)過程。
現(xiàn)在,我們分析下看使用了這種spi服務(wù)的模式原本的過程是怎樣的:

第一,從META-INF/services/java.sql.Driver文件中獲取具體的實(shí)現(xiàn)類名“com.mysql.jdbc.Driver”

第二,加載這個(gè)類,這里肯定只能用class.forName("com.mysql.jdbc.Driver")來加載

好了,問題來了,Class.forName()加載用的是調(diào)用者的Classloader,這個(gè)調(diào)用者DriverManager是在rt.jar中的,ClassLoader是啟動(dòng)類加載器,而com.mysql.jdbc.Driver肯定不在/lib下,所以肯定是無法加載mysql中的這個(gè)類的。這就是雙親委派模型的局限性了,父級(jí)加載器無法加載子級(jí)類加載器路徑中的類。

那么,這個(gè)問題如何解決呢?按照目前情況來分析,這個(gè)mysql的drvier只有應(yīng)用類加載器能加載,那么我們只要在啟動(dòng)類加載器中有方法獲取應(yīng)用程序類加載器,然后通過它去加載就可以了。這就是所謂的線程上下文加載器。

文章前半段提到線程上下文類加載器可以通過 Thread.setContextClassLoaser() 方法設(shè)置,如果不特殊設(shè)置會(huì)從父類繼承,一般默認(rèn)使用的是應(yīng)用程序類加載器

很明顯,線程上下文類加載器讓父級(jí)類加載器能通過調(diào)用子級(jí)類加載器來加載類,這打破了雙親委派模型的原則

現(xiàn)在我們看下DriverManager是如何使用線程上下文類加載器去加載第三方j(luò)ar包中的Driver類的,先來看源碼:

使用時(shí),我們直接調(diào)用DriverManager.getConnection() 方法自然會(huì)觸發(fā)靜態(tài)代碼塊的執(zhí)行,開始加載驅(qū)動(dòng)然后我們看下ServiceLoader.load()的具體實(shí)現(xiàn):

繼續(xù)向下看構(gòu)造函數(shù)實(shí)例化 ServiceLoader 做了哪些事情:

查看 reload() 函數(shù):

繼續(xù)查看LazyIterator構(gòu)造器,該類同樣實(shí)現(xiàn)了Iterator接口:

實(shí)例化到這里我們也將上下文得到的類加載器實(shí)例化到這里,來回看ServiceLoader 重寫的 iterator() 方法:

上面next() 方法調(diào)用了lookupIterator.next(),這個(gè)lookupIterator 就是剛剛實(shí)例化的 LazyIterator(); 來看next方法

繼續(xù)查看nextService 方法:

終于到這里了,在上面 nextService函數(shù)中第8行調(diào)用了c = Class.forName(cn, false, loader) 方法,我們成功的做到了通過線程上下文類加載器拿到了應(yīng)用程序類加載器(或者自定義的然后塞到線程上下文中的),同時(shí)我們也查找到了廠商在子級(jí)的jar包中注冊(cè)的驅(qū)動(dòng)具體實(shí)現(xiàn)類名,這樣我們就可以成功的在rt.jar包中的DriverManager中成功的加載了放在第三方應(yīng)用程序包中的類了同時(shí)在第16行完成Driver的實(shí)例化,等同于new Driver(); 文章開頭的問題在理解到這里也迎刃而解了

JAVA熱部署實(shí)現(xiàn)

首先談一下何為熱部署(hotswap),熱部署是在不重啟 Java 虛擬機(jī)的前提下,能自動(dòng)偵測(cè)到 class 文件的變化,更新運(yùn)行時(shí) class 的行為。Java 類是通過 Java 虛擬機(jī)加載的,某個(gè)類的 class 文件在被 classloader 加載后,會(huì)生成對(duì)應(yīng)的 Class 對(duì)象,之后就可以創(chuàng)建該類的實(shí)例。默認(rèn)的虛擬機(jī)行為只會(huì)在啟動(dòng)時(shí)加載類,如果后期有一個(gè)類需要更新的話,單純替換編譯的 class 文件,Java 虛擬機(jī)是不會(huì)更新正在運(yùn)行的 class。如果要實(shí)現(xiàn)熱部署,最根本的方式是修改虛擬機(jī)的源代碼,改變 classloader 的加載行為,使虛擬機(jī)能監(jiān)聽 class 文件的更新,重新加載 class 文件,這樣的行為破壞性很大,為后續(xù)的 JVM 升級(jí)埋下了一個(gè)大坑。

另一種友好的方法是創(chuàng)建自己的 classloader 來加載需要監(jiān)聽的 class,這樣就能控制類加載的時(shí)機(jī),從而實(shí)現(xiàn)熱部署。

熱部署步驟:

銷毀自定義classloader(被該加載器加載的class也會(huì)自動(dòng)卸載);

更新class

使用新的ClassLoader去加載class

JVM中的Class只有滿足以下三個(gè)條件,才能被GC回收,也就是該Class被卸載(unload):

該類所有的實(shí)例都已經(jīng)被GC,也就是JVM中不存在該Class的任何實(shí)例。

加載該類的ClassLoader已經(jīng)被GC。

該類的java.lang.Class 對(duì)象沒有在任何地方被引用,如不能在任何地方通過反射訪問該類的方法

自定義類加載器

要?jiǎng)?chuàng)建用戶自己的類加載器,只需要繼承java.lang.ClassLoader類,然后覆蓋它的findClass(String name)方法即可,即指明如何獲取類的字節(jié)碼流。

如果要符合雙親委派規(guī)范,則重寫findClass方法(用戶自定義類加載邏輯);要破壞的話,重寫loadClass方法(雙親委派的具體邏輯實(shí)現(xiàn))

感謝與參考

非常感謝以下博文的作者,通過反復(fù)拜讀來了解雙親委派模型的原理

https://blog.csdn.net/u014634...

https://www.cnblogs.com/aspir...

https://www.cnblogs.com/gdpuz...

https://www.jianshu.com/p/09f...

https://www.cnblogs.com/yahok...

推薦閱讀

面試還不知道 BeanFactory 和 ApplicationContext 的區(qū)別?

Spring Bean 生命周期之"我從哪里來?",懂得這個(gè)很重要

Spring Bean 生命周期之"我要到哪里去?"

如何設(shè)計(jì)好的RESTful API

輕松高效玩轉(zhuǎn)DTO(Data Transfer Object)

后續(xù)會(huì)出一系列文章點(diǎn)亮上圖,同時(shí)進(jìn)行 Spring 知識(shí)點(diǎn)解釋與串聯(lián),在工作中充分利用 Spring 的特性
另外,還會(huì)推出 Java 多線程與 ElasticSearch 相關(guān)內(nèi)容

歡迎持續(xù)關(guān)注公眾號(hào):「日拱一兵」

前沿 Java 技術(shù)干貨分享

高效工具匯總

面試問題分析與解答

技術(shù)資料領(lǐng)取

持續(xù)關(guān)注,帶你像讀偵探小說一樣輕松趣味學(xué)習(xí) Java 技術(shù)棧相關(guān)知識(shí)

閱讀原文

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/75299.html

相關(guān)文章

  • 金三銀四,2019大廠Android高級(jí)工程師面試整理

    摘要:原文地址游客前言金三銀四,很多同學(xué)心里大概都準(zhǔn)備著年后找工作或者跳槽。最近有很多同學(xué)都在交流群里求大廠面試題。 最近整理了一波面試題,包括安卓JAVA方面的,目前大廠還是以安卓源碼,算法,以及數(shù)據(jù)結(jié)構(gòu)為主,有一些中小型公司也會(huì)問到混合開發(fā)的知識(shí),至于我為什么傾向于混合開發(fā),我的一句話就是走上編程之路,將來你要學(xué)不僅僅是這些,豐富自己方能與世接軌,做好全棧的裝備。 原文地址:游客kutd...

    tracymac7 評(píng)論0 收藏0
  • 深入理解虛擬機(jī)之虛擬機(jī)類加載機(jī)制

    摘要:最終形成可以被虛擬機(jī)最直接使用的類型的過程就是虛擬機(jī)的類加載機(jī)制。即重寫一個(gè)類加載器的方法驗(yàn)證驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。 《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第二版》讀書筆記與常見相關(guān)面試題總結(jié) 本節(jié)常見面試題(推薦帶著問題閱讀,問題答案在文中都有提到): 簡(jiǎn)單說說類加載過...

    MadPecker 評(píng)論0 收藏0
  • Java面試 32個(gè)核心必考點(diǎn)完全解析

    摘要:如問到是否使用某框架,實(shí)際是是問該框架的使用場(chǎng)景,有什么特點(diǎn),和同類可框架對(duì)比一系列的問題。這兩個(gè)方向的區(qū)分點(diǎn)在于工作方向的側(cè)重點(diǎn)不同。 [TOC] 這是一份來自嗶哩嗶哩的Java面試Java面試 32個(gè)核心必考點(diǎn)完全解析(完) 課程預(yù)習(xí) 1.1 課程內(nèi)容分為三個(gè)模塊 基礎(chǔ)模塊: 技術(shù)崗位與面試 計(jì)算機(jī)基礎(chǔ) JVM原理 多線程 設(shè)計(jì)模式 數(shù)據(jù)結(jié)構(gòu)與算法 應(yīng)用模塊: 常用工具集 ...

    JiaXinYi 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

Object

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<