Java?ClassLoader虛擬類實現(xiàn)代碼熱替換的示例代碼
更新時間:2022年06月29日 09:59:41 作者:lolxxs
本文主要介紹了Java?ClassLoader虛擬類實現(xiàn)代碼熱替換的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
總結
- 類加載器是負責加載類的對象。類ClassLoader是一個抽象類。給定類的全限定類名,類加載器應嘗試查找或生成構成該類定義的數(shù)據(jù)Class文件。典型的策略是將名稱轉換為文件名,然后從文件系統(tǒng)中讀取該名稱的類文件
- 每個Class對象都包含一個Class.getClassLoader()方法可以獲取到定義它的ClassLoader
- 數(shù)組類的Class對象不是由類加載器創(chuàng)建的,而是根據(jù)Java運行時的要求自動創(chuàng)建的。getClassLoader()返回的數(shù)組類的類裝入器與其元素類型的類裝入器相同,如果元素類型是基礎類型,則數(shù)組類沒有類裝入器。
- 除了加載類之外,類加載器還負責定位資源。資源是一些數(shù)據(jù)(例如 .class文件、配置數(shù)據(jù)或圖像)。資源通常與應用程序或庫一起打包,以便可以通過應用程序或庫中的代碼找到它們。
- ClassLoader類使用委托模型來搜索類和資源。ClassLoader的每個實例都有一個關聯(lián)的父類加載器。當請求查找類或資源時,ClassLoader實例通常會在嘗試查找類或資源本身之前,將對該類或資源的搜索委托給其父類裝入器。
- Java內(nèi)置類加載器
加載器名 | 方法名 | 作用 |
---|---|---|
Bootstrap class loader | 無 | 虛擬機的內(nèi)置類加載器,底層是用C++實現(xiàn)的,沒有父加載器。主要加載系統(tǒng)環(huán)境的一些jar包和.class文件。C/C++語言編寫,是虛擬機的一部分,無法在Java代碼中直接獲取它的引用。可以通過 System.getProperty(“sun.boot.class.path”)獲取其加載路徑下的文件 |
Platform class loader (也稱為ExtClassLoader) | getPlatformClassLoader() | 平臺類加載器,負責加載JDK中一些特殊的模塊。主要加載java.ext.dirs下的.class文件 |
System class loader (也稱為AppClassLoader) | getSystemClassLoader() | 系統(tǒng)類加載器,負責加載用戶類路徑上所指定的類庫。主要加載java.class.path下的.class文件,是面向用戶編寫類的類加載器,即自己寫的類或者引入的第三方庫通常由此加載器加載 |
- 通常Java虛擬機以依賴于平臺的方式從本地文件系統(tǒng)加載類。但是,有些類可能不是源于文件;它們可能來自其他來源,如網(wǎng)絡,也可能由應用程序構建。 方法defineClass(String name, byte[] b, int off, int len),將字節(jié)數(shù)組轉換為類class的實例。這個新定義的類的實例可以使用Class.newInstance()方法創(chuàng)建
- 類加載器創(chuàng)建的對象的方法和構造函數(shù)可以引用其他類。為了確定引用的類,Java虛擬機調(diào)用最初創(chuàng)建該類的類加載器的loadClass方法
ClassLoader 虛擬類方法
方法名 | 作用 |
---|---|
protected ClassLoader(String name, ClassLoader parent) | 創(chuàng)建指定名稱name的新類加載器,并使用指定的父類加載器parent進行委派 |
public String getName() | 返回此類加載器的名稱,如果此類加載器未命名,則返回null |
public Class loadClass(String name, boolean resolve) | 加載具有指定類名稱的類,resolve為true表示解析類引用。loadClass方法會先調(diào)用getClassLoadingLock方法獲取鎖,再調(diào)用findLoadedClass方法檢查類是否已加載,如果未加載,則往父加載器一直遞歸調(diào)用loadClass加載該類,如果父加載器也加載不了該類,才調(diào)用findClass方法獲取Class對象,而findClass是虛擬方法由子類實現(xiàn)。其實現(xiàn)使用defineClass(String name, byte[] b, int off, int len)方法可以將class文件的字節(jié)數(shù)組轉為Class對象,最后使用resolveClass方法進行類的鏈接。而由于獲取該字節(jié)數(shù)組的方法是很多樣的,所以類加載的方式也非常多樣,如本地加載、網(wǎng)絡加載、壓縮包中加載、自己構建Class文件 |
protected Object getClassLoadingLock(String className) | 返回類加載操作的鎖對象。 如果此ClassLoader對象注冊為支持并行,則該方法返回與指定類名 className關聯(lián)的專用對象。否則該方法將返回此ClassLoader對象,即同一時間一個ClassLoader只能加載一個類 |
protected final Class findLoadedClass(String name) | 如果Java虛擬機已將此加載程序記錄為具有給定全限定類名稱的類的初始加載程序,則返回具有給定全限定類名稱的類。否則返回 null |
protected Class findClass(String name) | 查找具有指定全限定類名的類。這個方法是空方法應該被子類重寫,并且被調(diào)用在檢查請求類的父類加載器之后,loadClass方法將調(diào)用這個方法 |
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) | 將字節(jié)數(shù)組轉換為具有給定保護域ProtectionDomain的類Class的實例。 如果指定的name以“java.”開頭,它只能由getPlatformClassLoader()獲取到的平臺類加載器或其祖先定義(define),否則將拋出SecurityException。如果name不是null,則它必須等于字節(jié)數(shù)組b指定的類的全限定類名稱,否則將拋出NoClassDefFoundError |
protected final Class<?> defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) | 將字節(jié)緩沖區(qū)ByteBuffer轉換為具有給定保護域ProtectionDomain的類Class的實例。其余和上面一樣 |
protected final void resolveClass(Class c) | 鏈接指定的類。類加載器可能會使用此方法鏈接類。如果類c已經(jīng)被鏈接,那么這個方法直接返回。 否則,將按照Java語言規(guī)范執(zhí)行一章中的描述鏈接該類 |
實現(xiàn)代碼熱替換
通過上面我們可以知道類加載流程是
- loadClass方法會先調(diào)用getClassLoadingLock方法獲取鎖
- 再調(diào)用findLoadedClass方法檢查類是否已加載,如果已經(jīng)加載則直接獲取到該Class類對象,再判斷該Class類對象是否需要鏈接(resolve),如果要鏈接進入resolveClass,鏈接完后直接返回。鏈接就是執(zhí)行類加載過程中的驗證、準備、解析這些過程
- 如果未加載,則往父加載器一直遞歸調(diào)用loadClass加載該類
- 如果父加載器也加載不了該類,才調(diào)用findClass方法獲取Class對象而findClass是空方法由子類重寫
- 其實現(xiàn)中會使用defineClass(String name, byte[] b, int off, int len)方法,該可以將class文件的字節(jié)數(shù)組轉為Class對象
- 如果需要鏈接,最后使用resolveClass方法進行類的鏈接
實現(xiàn)
- 如果我們要實現(xiàn)代碼熱替換,那么就要使用defineClass方法加載新的類,所以最簡單的實現(xiàn)就是直接使用defineClass方法將新的Class文件字節(jié)數(shù)組轉為Class對象,再使用反射創(chuàng)建新對象并執(zhí)行新方法
- 但是defineClass是protected方法,所以我們只能繼承ClassLoader虛擬類才能調(diào)用該方法
- 最好的規(guī)范就是繼承ClassLoader虛擬類,并實現(xiàn)其loadClass方法和findClass方法,并在loadClass方法中調(diào)用findClass方法,在findClass方法中再調(diào)用defineClass方法
- 為了便于理解,直接拋棄規(guī)范,直接自己寫一個方法直接調(diào)用defineClass方法實現(xiàn)代碼熱替換
項目結構,out是編譯出的class文件目錄,由于Test就在src目錄下,沒有包名,則其全限定類名為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ù)組轉為Class類對象 Test為全限定類名 Class clazz = test.defineClass("Test", bytes, 0 ,len); //使用反射根據(jù)新的Class對象創(chuàng)建新對象,并執(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"); } }
啟動后修改代碼,然后點重新編譯
可以看到代碼被熱替換了
改進思考
- 正常實現(xiàn)流程應該為繼承ClassLoader虛擬類,并重寫其loadClass方法和findClass方法,并在loadClass方法中調(diào)用findClass方法,在findClass方法中再調(diào)用defineClass方法
- 實現(xiàn)熱替換應該是替換修改過的代碼,則應當維護一個Map<String, Long> 存儲從全限定類名到上次文件修改時間的映射,每次定時掃描Class文件目錄或檢測到保存快捷鍵Ctrl+s時觸發(fā)掃描,文件的屬性也有上次修改時間,拿我們存儲的和文件的屬性比較即可知道文件是否修改,即是否需要重新加載Class類
- 熱替換產(chǎn)生了大量類信息都存儲在jdk1.7的永久代,jdk1.8的元空間,如果無用的類信息過多則會造成OOM,我們自定義類加載器和其產(chǎn)生的Class類對象,都可以通過置空(= null)使其不可達,然后調(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(); } }
到此這篇關于Java ClassLoader虛擬類實現(xiàn)代碼熱替換的示例代碼的文章就介紹到這了,更多相關Java ClassLoader代碼熱替換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
spring boot攔截器實現(xiàn)IP黑名單實例代碼
本篇文章主要介紹了spring boot攔截器實現(xiàn)IP黑名單實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04Java實現(xiàn)文件壓縮與解壓的示例[zip格式,gzip格式]
本篇文章主要介紹了Java實現(xiàn)文件壓縮與解壓的示例[zip格式,gzip格式],具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01