摘要:如何自己手寫一個熱加載熱加載在不停止程序運行的情況下,對類對象的動態替換簡述中的類從被加載到內存中到卸載出內存為止,一共經歷了七個階段加載驗證準備解析初始化使用卸載。并形成一個父子結構。
如何自己手寫一個熱加載
熱加載:在不停止程序運行的情況下,對類(對象)的動態替換Java ClassLoader 簡述
Java中的類從被加載到內存中到卸載出內存為止,一共經歷了七個階段:加載、驗證、準備、解析、初始化、使用、卸載。
接下來我們重點講解加載和初始化這兩步
加載在加載的階段,虛擬機需要完成以下三件事:
通過一個類的全限定名來獲取定義此類的二進制字節流
將這個字節流所代表的的靜態存儲結構轉化為方法區的運行時數據結構
在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。
這三步都是通過類加載器來實現的。而官方定義的Java類加載器有BootstrapClassLoader、ExtClassLoader、AppClassLoader。這三個類加載器分別負責加載不同路徑的類的加載。并形成一個父子結構。
類加載器名稱 | 負責加載目錄 |
---|---|
BootstrapClassLoader | 處于類加載器層次結構的最高層,負責 sun.boot.class.path 路徑下類的加載,默認為 jre/lib 目錄下的核心 API 或 -Xbootclasspath 選項指定的 jar 包 |
ExtClassLoader | 加載路徑為 java.ext.dirs,默認為 jre/lib/ext 目錄或者 -Djava.ext.dirs 指定目錄下的 jar 包加載 |
AppClassLoader | 加載路徑為 java.class.path,默認為環境變量 CLASSPATH 中設定的值。也可以通過 -classpath 選型進行指定 |
默認情況下,例如我們使用關鍵字new或者Class.forName都是通過AppClassLoader 類加載器來加載的
正因為是此父子結構,所以默認情況下如果要加載一個類,會優先將此類交給其父類進行加載(直到頂層的BootstrapClassLoader 也沒有),如果父類都沒有,那么才會將此類交給子類加載。這就是類加載器的雙親委派規則。
初始化當我們要使用一個類的執行方法或者屬性時,類必須是加載到內存中并且完成初始化的。那么類是什么時候被初始化的呢?有以下幾種情況
使用new關鍵字實例化對象的時候、讀取或者設置一個類的靜態字段、以及調用一個類的靜態方法。
使用java.lang.reflect包的方法對類進行反射調用時,如果類沒有進行初始化,那么先進行初始化。
初始化一個類的時候,如果發現其父類沒有進行初始化,則先觸發父類的初始化。
當虛擬機啟動時,用戶需要制定一個執行的主類(包含main()方法的那個類)虛擬機會先初始化這個主類。
如何實現熱加載?在上面我們知道了在默認情況下,類加載器是遵循雙親委派規則的。所以我們要實現熱加載,那么我們需要加載的那些類就不能交給系統加載器來完成。所以我們要自定義類加載器來寫我們自己的規則。
實現自己的類加載器要想實現自己的類加載器,只需要繼承ClassLoader類即可。而我們要打破雙親委派規則,那么我們就必須要重寫loadClass方法,因為默認情況下loadClass方法是遵循雙親委派的規則的。
public class CustomClassLoader extends ClassLoader{ private static final String CLASS_FILE_SUFFIX = ".class"; //AppClassLoader的父類加載器 private ClassLoader extClassLoader; public CustomClassLoader(){ ClassLoader j = String.class.getClassLoader(); if (j == null) { j = getSystemClassLoader(); while (j.getParent() != null) { j = j.getParent(); } } this.extClassLoader = j ; } protected Class> loadClass(String name, boolean resolve){ Class cls = null; cls = findLoadedClass(name); if (cls != null){ return cls; } //獲取ExtClassLoader ClassLoader extClassLoader = getExtClassLoader() ; //確保自定義的類不會覆蓋Java的核心類 try { cls = extClassLoader.loadClass(name); if (cls != null){ return cls; } }catch (ClassNotFoundException e ){ } cls = findClass(name); return cls; } @Override public Class> findClass(String name) { byte[] bt = loadClassData(name); return defineClass(name, bt, 0, bt.length); } private byte[] loadClassData(String className) { // 讀取Class文件呢 InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".", "/")+CLASS_FILE_SUFFIX); ByteArrayOutputStream byteSt = new ByteArrayOutputStream(); // 寫入byteStream int len =0; try { while((len=is.read())!=-1){ byteSt.write(len); } } catch (IOException e) { e.printStackTrace(); } // 轉換為數組 return byteSt.toByteArray(); } public ClassLoader getExtClassLoader(){ return extClassLoader; } }
為什么要先獲取ExtClassLoader類加載器呢?其實這里是借鑒了Tomcat里面的設計,是為了避免我們自定義的類加載器覆蓋了一些核心類。例如java.lang.Object。
為什么是獲取ExtClassLoader 類加載器而不是獲取AppClassLoader 呢?這是因為如果我們獲取了AppClassLoader 進行加載,那么不還是雙親委派的規則了嗎?
監控class文件這里我們使用ScheduledThreadPoolExecutor 來進行周期性的監控文件是否修改。在程序啟動的時候記錄文件的最后修改時間。隨后周期性的查看文件的最后修改時間是否改動。如果改動了那么就重新生成類加載器進行替換。這樣新的文件就被加載進內存中了。
首先我們建立一個需要監控的文件
public class Test { public void test(){ System.out.println("Hello World! Version one"); } }
我們通過在程序運行時修改版本號,來動態的輸出版本號。接下來我們建立周期性執行的任務類。
public class WatchDog implements Runnable{ private MapfileDefineMap; public WatchDog(Map fileDefineMap){ this.fileDefineMap = fileDefineMap; } @Override public void run() { File file = new File(FileDefine.WATCH_PACKAGE); File[] files = file.listFiles(); for (File watchFile : files){ long newTime = watchFile.lastModified(); FileDefine fileDefine = fileDefineMap.get(watchFile.getName()); long oldTime = fileDefine.getLastDefine(); //如果文件被修改了,那么重新生成累加載器加載新文件 if (newTime!=oldTime){ fileDefine.setLastDefine(newTime); loadMyClass(); } } } public void loadMyClass(){ try { CustomClassLoader customClassLoader = new CustomClassLoader(); Class> cls = customClassLoader.loadClass("com.example.watchfile.Test",false); Object test = cls.newInstance(); Method method = cls.getMethod("test"); method.invoke(test); }catch (Exception e){ System.out.println(e); } } }
可以看到在上面的gif演示圖中我們簡單的實現了熱加載的功能。
優化在上面的方法調用中我們是使用了getMethod()方法來調用的。此時或許會有疑問,為什么不直接將newInstance()強轉為Test類呢?
如果我們使用了強轉的話,代碼會變成這樣Test test = (Test) cls.newInstance()。但是在運行的時候會拋ClassCastException 異常。這是為什么呢?因為在Java中確定兩個類是否相等,除了看他們兩個類文件是否相同以外還會看他們的類加載器是否相同。所以即使是同一個類文件,如果是兩個不同的類加載器來加載的,那么它們的類型就是不同的。
WatchDog類是由我們new出來的。所以默認是AppClassLoader 來加載的。所以test 變量的聲明類型是WatchDog 方法中的一個屬性,所以也是由AppClassLoader 來加載的。因此兩個類不相同。
該如何解決呢?問題就出在了=號雙方的類不一樣,那么我們給它搞成一樣不就行了嗎?怎么搞?答案就是接口。默認情況下,如果我們實現了一個接口,那么此接口一般都是以子類的加載器為主的。意思就是如果沒有特殊要求的話,例如A implements B 如果A的加載器是自定義的。那么B接口的加載器也是和子類是一樣的。
所以我們要將接口的類加載器搞成是AppClassLoader 來加載。所以自定義加載器中加入這一句
if ("com.example.watchfile.ITest".equals(name)){ try { cls = getSystemClassLoader().loadClass(name); } catch (ClassNotFoundException e) { } return cls; }
建立接口
public interface ITest { void test(); }
這樣我們就能愉快的調用了。直接調用其方法。不會拋異常,因為=號雙方的類是一樣的。
CustomClassLoader customClassLoader = new CustomClassLoader(); Class> cls = customClassLoader.loadClass("com.example.watchfile.Test",false); ITest test = (ITest) cls.newInstance(); test.test();源代碼地址Github 參考文章
https://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/index.html
[Java虛擬機]()
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75370.html
摘要:馬上要出了,完全手寫一個優化后的腳手架是不可或缺的技能。每個依賴項隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節詳細討論這個過程。的事件流機制保證了插件的有序性,使得整個系統擴展性很好。 webpack馬上要出5了,完全手寫一個優化后的腳手架是不可或缺的技能。 本文書寫時間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗過可...
摘要:馬上要出了,完全手寫一個優化后的腳手架是不可或缺的技能。每個依賴項隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節詳細討論這個過程。的事件流機制保證了插件的有序性,使得整個系統擴展性很好。 webpack馬上要出5了,完全手寫一個優化后的腳手架是不可或缺的技能。 本文書寫時間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗過可...
摘要:馬上要出了,完全手寫一個優化后的腳手架是不可或缺的技能。每個依賴項隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節詳細討論這個過程。的事件流機制保證了插件的有序性,使得整個系統擴展性很好。 webpack馬上要出5了,完全手寫一個優化后的腳手架是不可或缺的技能。 本文書寫時間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗過可...
摘要:找工作之前看了很多面試題,復習資料,但是發現純看面試題是不行的,因為靠背的東西是記不牢的,需要知識成體系才可以,所以筆者整理了一份復習大綱,基本涵蓋了中高級工程師面試所必須知識點,希望可以通過此文幫助一些想換工作的朋友更好的復習,準備面試。 概述 都說金三銀四青銅五,這幾個月份是程序員最好的跳槽時間,筆者四月初也換了工作。找工作之前看了很多面試題,復習資料,但是發現純看面試題是不行的,因為靠...
閱讀 2000·2023-04-25 16:53
閱讀 1442·2021-10-13 09:39
閱讀 606·2021-09-08 09:35
閱讀 1639·2019-08-30 13:03
閱讀 2121·2019-08-30 11:06
閱讀 1831·2019-08-30 10:59
閱讀 3188·2019-08-29 17:00
閱讀 2288·2019-08-23 17:55