Java?ClassLoader虛擬類實(shí)現(xiàn)代碼熱替換的示例代碼
總結(jié)
- 類加載器是負(fù)責(zé)加載類的對(duì)象。類ClassLoader是一個(gè)抽象類。給定類的全限定類名,類加載器應(yīng)嘗試查找或生成構(gòu)成該類定義的數(shù)據(jù)Class文件。典型的策略是將名稱轉(zhuǎn)換為文件名,然后從文件系統(tǒng)中讀取該名稱的類文件
- 每個(gè)Class對(duì)象都包含一個(gè)Class.getClassLoader()方法可以獲取到定義它的ClassLoader
- 數(shù)組類的Class對(duì)象不是由類加載器創(chuàng)建的,而是根據(jù)Java運(yùn)行時(shí)的要求自動(dòng)創(chuàng)建的。getClassLoader()返回的數(shù)組類的類裝入器與其元素類型的類裝入器相同,如果元素類型是基礎(chǔ)類型,則數(shù)組類沒(méi)有類裝入器。
- 除了加載類之外,類加載器還負(fù)責(zé)定位資源。資源是一些數(shù)據(jù)(例如 .class文件、配置數(shù)據(jù)或圖像)。資源通常與應(yīng)用程序或庫(kù)一起打包,以便可以通過(guò)應(yīng)用程序或庫(kù)中的代碼找到它們。
- ClassLoader類使用委托模型來(lái)搜索類和資源。ClassLoader的每個(gè)實(shí)例都有一個(gè)關(guān)聯(lián)的父類加載器。當(dāng)請(qǐng)求查找類或資源時(shí),ClassLoader實(shí)例通常會(huì)在嘗試查找類或資源本身之前,將對(duì)該類或資源的搜索委托給其父類裝入器。
- Java內(nèi)置類加載器
加載器名 | 方法名 | 作用 |
---|---|---|
Bootstrap class loader | 無(wú) | 虛擬機(jī)的內(nèi)置類加載器,底層是用C++實(shí)現(xiàn)的,沒(méi)有父加載器。主要加載系統(tǒng)環(huán)境的一些jar包和.class文件。C/C++語(yǔ)言編寫,是虛擬機(jī)的一部分,無(wú)法在Java代碼中直接獲取它的引用。可以通過(guò) System.getProperty(“sun.boot.class.path”)獲取其加載路徑下的文件 |
Platform class loader (也稱為ExtClassLoader) | getPlatformClassLoader() | 平臺(tái)類加載器,負(fù)責(zé)加載JDK中一些特殊的模塊。主要加載java.ext.dirs下的.class文件 |
System class loader (也稱為AppClassLoader) | getSystemClassLoader() | 系統(tǒng)類加載器,負(fù)責(zé)加載用戶類路徑上所指定的類庫(kù)。主要加載java.class.path下的.class文件,是面向用戶編寫類的類加載器,即自己寫的類或者引入的第三方庫(kù)通常由此加載器加載 |
- 通常Java虛擬機(jī)以依賴于平臺(tái)的方式從本地文件系統(tǒng)加載類。但是,有些類可能不是源于文件;它們可能來(lái)自其他來(lái)源,如網(wǎng)絡(luò),也可能由應(yīng)用程序構(gòu)建。 方法defineClass(String name, byte[] b, int off, int len),將字節(jié)數(shù)組轉(zhuǎn)換為類class的實(shí)例。這個(gè)新定義的類的實(shí)例可以使用Class.newInstance()方法創(chuàng)建
- 類加載器創(chuàng)建的對(duì)象的方法和構(gòu)造函數(shù)可以引用其他類。為了確定引用的類,Java虛擬機(jī)調(diào)用最初創(chuàng)建該類的類加載器的loadClass方法
ClassLoader 虛擬類方法
方法名 | 作用 |
---|---|
protected ClassLoader(String name, ClassLoader parent) | 創(chuàng)建指定名稱name的新類加載器,并使用指定的父類加載器parent進(jìn)行委派 |
public String getName() | 返回此類加載器的名稱,如果此類加載器未命名,則返回null |
public Class loadClass(String name, boolean resolve) | 加載具有指定類名稱的類,resolve為true表示解析類引用。loadClass方法會(huì)先調(diào)用getClassLoadingLock方法獲取鎖,再調(diào)用findLoadedClass方法檢查類是否已加載,如果未加載,則往父加載器一直遞歸調(diào)用loadClass加載該類,如果父加載器也加載不了該類,才調(diào)用findClass方法獲取Class對(duì)象,而findClass是虛擬方法由子類實(shí)現(xiàn)。其實(shí)現(xiàn)使用defineClass(String name, byte[] b, int off, int len)方法可以將class文件的字節(jié)數(shù)組轉(zhuǎn)為Class對(duì)象,最后使用resolveClass方法進(jìn)行類的鏈接。而由于獲取該字節(jié)數(shù)組的方法是很多樣的,所以類加載的方式也非常多樣,如本地加載、網(wǎng)絡(luò)加載、壓縮包中加載、自己構(gòu)建Class文件 |
protected Object getClassLoadingLock(String className) | 返回類加載操作的鎖對(duì)象。 如果此ClassLoader對(duì)象注冊(cè)為支持并行,則該方法返回與指定類名 className關(guān)聯(lián)的專用對(duì)象。否則該方法將返回此ClassLoader對(duì)象,即同一時(shí)間一個(gè)ClassLoader只能加載一個(gè)類 |
protected final Class findLoadedClass(String name) | 如果Java虛擬機(jī)已將此加載程序記錄為具有給定全限定類名稱的類的初始加載程序,則返回具有給定全限定類名稱的類。否則返回 null |
protected Class findClass(String name) | 查找具有指定全限定類名的類。這個(gè)方法是空方法應(yīng)該被子類重寫,并且被調(diào)用在檢查請(qǐng)求類的父類加載器之后,loadClass方法將調(diào)用這個(gè)方法 |
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) | 將字節(jié)數(shù)組轉(zhuǎn)換為具有給定保護(hù)域ProtectionDomain的類Class的實(shí)例。 如果指定的name以“java.”開(kāi)頭,它只能由getPlatformClassLoader()獲取到的平臺(tái)類加載器或其祖先定義(define),否則將拋出SecurityException。如果name不是null,則它必須等于字節(jié)數(shù)組b指定的類的全限定類名稱,否則將拋出NoClassDefFoundError |
protected final Class<?> defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) | 將字節(jié)緩沖區(qū)ByteBuffer轉(zhuǎn)換為具有給定保護(hù)域ProtectionDomain的類Class的實(shí)例。其余和上面一樣 |
protected final void resolveClass(Class c) | 鏈接指定的類。類加載器可能會(huì)使用此方法鏈接類。如果類c已經(jīng)被鏈接,那么這個(gè)方法直接返回。 否則,將按照J(rèn)ava語(yǔ)言規(guī)范執(zhí)行一章中的描述鏈接該類 |
實(shí)現(xiàn)代碼熱替換
通過(guò)上面我們可以知道類加載流程是
- loadClass方法會(huì)先調(diào)用getClassLoadingLock方法獲取鎖
- 再調(diào)用findLoadedClass方法檢查類是否已加載,如果已經(jīng)加載則直接獲取到該Class類對(duì)象,再判斷該Class類對(duì)象是否需要鏈接(resolve),如果要鏈接進(jìn)入resolveClass,鏈接完后直接返回。鏈接就是執(zhí)行類加載過(guò)程中的驗(yàn)證、準(zhǔn)備、解析這些過(guò)程
- 如果未加載,則往父加載器一直遞歸調(diào)用loadClass加載該類
- 如果父加載器也加載不了該類,才調(diào)用findClass方法獲取Class對(duì)象而findClass是空方法由子類重寫
- 其實(shí)現(xiàn)中會(huì)使用defineClass(String name, byte[] b, int off, int len)方法,該可以將class文件的字節(jié)數(shù)組轉(zhuǎn)為Class對(duì)象
- 如果需要鏈接,最后使用resolveClass方法進(jìn)行類的鏈接
實(shí)現(xiàn)
- 如果我們要實(shí)現(xiàn)代碼熱替換,那么就要使用defineClass方法加載新的類,所以最簡(jiǎn)單的實(shí)現(xiàn)就是直接使用defineClass方法將新的Class文件字節(jié)數(shù)組轉(zhuǎn)為Class對(duì)象,再使用反射創(chuàng)建新對(duì)象并執(zhí)行新方法
- 但是defineClass是protected方法,所以我們只能繼承ClassLoader虛擬類才能調(diào)用該方法
- 最好的規(guī)范就是繼承ClassLoader虛擬類,并實(shí)現(xiàn)其loadClass方法和findClass方法,并在loadClass方法中調(diào)用findClass方法,在findClass方法中再調(diào)用defineClass方法
- 為了便于理解,直接拋棄規(guī)范,直接自己寫一個(gè)方法直接調(diào)用defineClass方法實(shí)現(xiàn)代碼熱替換
項(xiàng)目結(jié)構(gòu),out是編譯出的class文件目錄,由于Test就在src目錄下,沒(méi)有包名,則其全限定類名為Test
public class Test extends ClassLoader { public static void main(String[] args) throws Exception { while(true) { try { Test test = new Test(); // 編譯后的class文件位置 ./表示代碼根目錄 String classFile = "./out/production/Java_hot_replace/Test.class"; FileInputStream fis = new FileInputStream(classFile); byte[] bytes = new byte[1024*10]; int len = fis.read(bytes); //將字節(jié)數(shù)組轉(zhuǎn)為Class類對(duì)象 Test為全限定類名 Class clazz = test.defineClass("Test", bytes, 0 ,len); //使用反射根據(jù)新的Class對(duì)象創(chuàng)建新對(duì)象,并執(zhí)行其printStr方法 Object object = clazz.newInstance(); Method m = object.getClass().getMethod("printStr", new Class[] {}); m.invoke(object, new Object[] {}); Thread.sleep(2000); } catch(Exception e) { e.printStackTrace(); try { Thread.sleep(2000); } catch(InterruptedException ex) { } } } } public void printStr() { System.out.println("A"); } }
啟動(dòng)后修改代碼,然后點(diǎn)重新編譯
可以看到代碼被熱替換了
改進(jìn)思考
- 正常實(shí)現(xiàn)流程應(yīng)該為繼承ClassLoader虛擬類,并重寫其loadClass方法和findClass方法,并在loadClass方法中調(diào)用findClass方法,在findClass方法中再調(diào)用defineClass方法
- 實(shí)現(xiàn)熱替換應(yīng)該是替換修改過(guò)的代碼,則應(yīng)當(dāng)維護(hù)一個(gè)Map<String, Long> 存儲(chǔ)從全限定類名到上次文件修改時(shí)間的映射,每次定時(shí)掃描Class文件目錄或檢測(cè)到保存快捷鍵Ctrl+s時(shí)觸發(fā)掃描,文件的屬性也有上次修改時(shí)間,拿我們存儲(chǔ)的和文件的屬性比較即可知道文件是否修改,即是否需要重新加載Class類
- 熱替換產(chǎn)生了大量類信息都存儲(chǔ)在jdk1.7的永久代,jdk1.8的元空間,如果無(wú)用的類信息過(guò)多則會(huì)造成OOM,我們自定義類加載器和其產(chǎn)生的Class類對(duì)象,都可以通過(guò)置空(= null)使其不可達(dá),然后調(diào)用System.gc()就可以卸載,即類似如下代碼
public class Test extends ClassLoader { public static void main(String[] args) throws Exception { MyClassLoader classLoader = new MyClassLoader(); Class classLoaded = classLoader.loadClass("MyClass"); classLoaded = null; classLoader = null; System.gc(); } }
到此這篇關(guān)于Java ClassLoader虛擬類實(shí)現(xiàn)代碼熱替換的示例代碼的文章就介紹到這了,更多相關(guān)Java ClassLoader代碼熱替換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java數(shù)據(jù)結(jié)構(gòu)之稀疏矩陣定義與用法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之稀疏矩陣定義與用法,結(jié)合實(shí)例形式分析了java稀疏矩陣的定義、運(yùn)算、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01關(guān)于SpringBoot自定義條件注解與自動(dòng)配置
這篇文章主要介紹了關(guān)于SpringBoot自定義條件注解與自動(dòng)配置,Spring Boot的核心功能就是為整合第三方框架提供自動(dòng)配置,而本文則帶著大家實(shí)現(xiàn)了自己的自動(dòng)配置和Starter,需要的朋友可以參考下2023-07-07JAVA中StackOverflowError錯(cuò)誤的解決
這篇文章主要介紹了JAVA中StackOverflowError錯(cuò)誤的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04解決springcloud Zuul丟失Cookie的問(wèn)題
這篇文章主要介紹了解決springcloud Zuul丟失Cookie的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10spring boot攔截器實(shí)現(xiàn)IP黑名單實(shí)例代碼
本篇文章主要介紹了spring boot攔截器實(shí)現(xiàn)IP黑名單實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Java實(shí)現(xiàn)文件壓縮與解壓的示例[zip格式,gzip格式]
本篇文章主要介紹了Java實(shí)現(xiàn)文件壓縮與解壓的示例[zip格式,gzip格式],具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-01-01struts2簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Struts2框架是MVC流程框架,適合分層開(kāi)發(fā),這篇文章主要為大家詳細(xì)介紹了struts2簡(jiǎn)介的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Java實(shí)現(xiàn)驗(yàn)證文件名有效性的方法詳解
在本文中,我們將討論使用?Java?驗(yàn)證一個(gè)給定的字符串是否具有操作系統(tǒng)的有效文件名的不同方法,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-09-09