- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- 詳解Java類(lèi)動(dòng)態(tài)加載和熱替換
最近,遇到了兩個(gè)和Java類(lèi)的加載和卸載相關(guān)的問(wèn)題:
1) 是一道關(guān)于Java的判斷題:一個(gè)類(lèi)被首次加載后,會(huì )長(cháng)期留駐JVM,直到JVM退出。這個(gè)說(shuō)法,是不是正確的?
2) 在開(kāi)發(fā)的一個(gè)集成平臺中,需要集成類(lèi)似接口的多種工具,并且工具可能會(huì )有新增,同時(shí)在不同的環(huán)境部署會(huì )有裁剪(例如對外提供服務(wù)的應用,不能提供特定的采購的工具),如何才能更好地實(shí)現?
針對上面的第2點(diǎn),我們采用Java插件化開(kāi)發(fā)實(shí)現。上面的兩個(gè)問(wèn)題,都和Java的類(lèi)加載和熱替換機制有關(guān)。
類(lèi)加載器,顧名思義,就是用來(lái)實(shí)現類(lèi)的加載操作。每個(gè)類(lèi)加載器都有一個(gè)獨立的類(lèi)名稱(chēng)空間,就是說(shuō)每個(gè)由該類(lèi)加載器加載的類(lèi),都在自己的類(lèi)名稱(chēng)空間,如果要比較兩個(gè)類(lèi)是否“相等”,首先這兩個(gè)類(lèi)必須在相同的類(lèi)命名空間,即由相同的類(lèi)加載器加載(即對于任何一個(gè)類(lèi),都必須由該類(lèi)本身和加載它的類(lèi)加載器一起確定其在JVM中的唯一性),不是同一個(gè)類(lèi)加載器加載的類(lèi),不會(huì )相等。
在Java中,主要有如下的類(lèi)加載器:
下面,簡(jiǎn)單介紹上面這幾種類(lèi)加載器:
雙親委派模型,是從 Java1.2 開(kāi)始引入的一種類(lèi)加載器模式,在Java中,類(lèi)的加載操作通過(guò)java.lang.ClassLoader中的loadClass()方法完成,咱們首先看看該方法的實(shí)現(直接從Java源碼中撈出來(lái)的):
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
我們結合上面的注釋?zhuān)瑏?lái)解釋下雙親委派模型的內容:
1) 接收到一個(gè)類(lèi)加載請求后,首先判斷該類(lèi)是否有加載,如果已經(jīng)加載,則直接返回;
2) 如果尚未加載,首先獲取父類(lèi)加載器,如果可以獲取父類(lèi)加載器,則調用父類(lèi)的loadClass()方法來(lái)加載該類(lèi),如果無(wú)法獲取父類(lèi)加載器,則調用啟動(dòng)器加載器來(lái)加載該類(lèi);
3) 判斷該類(lèi)是否被父類(lèi)加載器或者啟動(dòng)類(lèi)加載器加載,如果已經(jīng)加載完成則返回,如果未成功加載,則自己嘗試來(lái)加載該類(lèi)。
上面的描述,說(shuō)明了loadClass()方法的實(shí)現,我們進(jìn)一步對上面的步驟進(jìn)行解釋?zhuān)?/p>
最后啰嗦一下,再進(jìn)行一下總結:
雙親委派模型:如果一個(gè)類(lèi)加載器收到類(lèi)加載請求,會(huì )首先把加載請求委派給父類(lèi)加載器完成,每個(gè)層次的類(lèi)加載器都是這樣,最終所有的加載請求都傳動(dòng)到最根的啟動(dòng)類(lèi)加載器來(lái)完成,如果父類(lèi)加載器無(wú)法完成該加載請求(即自己加載的范圍內找不到該類(lèi)),子類(lèi)加載器才會(huì )嘗試自己加載。
這樣的雙親委派模型有個(gè)好處:就是所有的類(lèi)都盡可能由頂層的類(lèi)加載器加載,保證了加載的類(lèi)的唯一性,如果每個(gè)類(lèi)都隨機由不同的類(lèi)加載器加載,則類(lèi)的實(shí)現關(guān)系無(wú)法保證,對于保證Java程序的穩定運行意義重大。
在Java中,每個(gè)類(lèi)都有相應的Class Loader,同樣的,每個(gè)實(shí)例對象也會(huì )有相應的類(lèi),當滿(mǎn)足如下三個(gè)條件時(shí),JVM就會(huì )卸載這個(gè)類(lèi):
1) 該類(lèi)所有實(shí)例對象不可達
2) 該類(lèi)的Class對象不可達
3) 該類(lèi)的Class Loader不可達
那么,上面示例對象、Class對象和類(lèi)的Class Loader直接是什么關(guān)系呢?
在類(lèi)加載器的內部實(shí)現中,用一個(gè)Java集合來(lái)存放所加載類(lèi)的引用。而一個(gè)Class對象總是會(huì )引用它的類(lèi)加載器,調用Class對象的getClassLoader()方法,就能獲得它的類(lèi)加載器。所以,Class實(shí)例和加載它的加載器之間為雙向引用關(guān)系。
一個(gè)類(lèi)的實(shí)例總是引用代表這個(gè)類(lèi)的Class對象。在Object類(lèi)中定義了getClass()方法,這個(gè)方法返回代表對象所屬類(lèi)的Class對象的引用。此外,所有的Java類(lèi)都有一個(gè)靜態(tài)屬性class,它引用代表這個(gè)類(lèi)的Class對象。
Java虛擬機自帶的類(lèi)加載器(前面介紹的三種類(lèi)加載器)在JVM運行過(guò)程中,會(huì )始終存在,而這些類(lèi)加載器則會(huì )始終引用它們所加載的類(lèi)的Class對象,因此這些Class對象始終是可觸及的。因此,由Java虛擬機自帶的類(lèi)加載器所加載的類(lèi),在虛擬機的生命周期中,始終不會(huì )被卸載。
那么,我們是不是就完全不能在Java程序運行過(guò)程中,動(dòng)態(tài)修改我們使用的類(lèi)了嗎?答案是否定的!根據上面的分析,通過(guò)Java虛擬機自帶的類(lèi)加載器加載的類(lèi)無(wú)法卸載,我們可以自定義類(lèi)加載器來(lái)加載Java程序,通過(guò)自定義類(lèi)加載器加載的Java類(lèi),是可以被卸載的。
前面介紹到,類(lèi)加載的雙親委派模型,是推薦模型,在loadClass中實(shí)現的,并不是必須使用的模型。我們可以通過(guò)自定義類(lèi)加載器,直接加載我們需要的Java類(lèi),而不委托給父類(lèi)加載器。
如上圖所示,我們有自定義的類(lèi)加載器MyClassLoader,用來(lái)加載類(lèi)MyClass,則在JVM中,會(huì )存在上面三類(lèi)引用(上圖忽略這三種類(lèi)型對象對其他的對象的引用)。如果我們將左邊的三個(gè)引用變量,均設置為null,那么此時(shí),已經(jīng)加載的MyClass將會(huì )被卸載。
動(dòng)態(tài)卸載需要借助于JVM的垃圾收集功能才可以做到,但是我們知道,JVM的垃圾回收,只有在堆內存占用比較高的時(shí)候,才會(huì )觸發(fā)。即使我們調用了System.gc(),也不會(huì )立即執行垃圾回收操作,而只是告訴JVM需要執行垃圾回收,至于什么時(shí)候垃圾回收,則要看JVM自己的垃圾回收策略。
但是我們不需要悲觀(guān),即使動(dòng)態(tài)卸載不是那么牢靠,但是實(shí)現動(dòng)態(tài)的Java類(lèi)的熱替換還是有希望的。
下面通過(guò)代碼來(lái)介紹Java類(lèi)的熱替換方法(代碼簡(jiǎn)陋,主要為了說(shuō)明問(wèn)題):
如下面的代碼:
首先定義一個(gè)自定義類(lèi)加載器:
package zmj; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class FileClassLoader extends ClassLoader { private String fileName; public void setFileName(String fileName) { this.fileName = fileName; } public Class loadClass(String name) throws ClassNotFoundException { if (name.startsWith("java")) { return getSystemClassLoader().loadClass(name); } Class cls = null; File classF = new File(fileName); try { cls = instantiateClass(name, new FileInputStream(classF), classF.length()); } catch (IOException e) { e.printStackTrace(); } return cls; } private Class instantiateClass(String name, InputStream fin, long len) throws IOException { byte[] raw = new byte[(int) len]; fin.read(raw); fin.close(); return defineClass(name, raw, 0, raw.length); } }
上面在loadClass時(shí),先判斷類(lèi)name(包含package的全限定名)是否以java開(kāi)始,如果是java開(kāi)始,則使用JVM自帶的類(lèi)加載器加載。
然后定義一個(gè)簡(jiǎn)單的動(dòng)態(tài)加載類(lèi):
package zmj; public class SayHello { public void say() { System.out.println("hello ping..."); } }
在執行過(guò)程中,會(huì )動(dòng)態(tài)修改打印內容,測試類(lèi)的熱加載。
然后定義一個(gè)調用類(lèi):
package zmj; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws InterruptedException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { while (true) { FileClassLoader fileClassLoader = new FileClassLoader(); fileClassLoader.setFileName("D:/workspace/idea/test/class-loader-test/target/classes/zmj/SayHello.class"); Object obj = null; obj = fileClassLoader.loadClass("zmj.SayHello").newInstance(); Method m = obj.getClass().getMethod("say", new Class[]{}); m.invoke(obj, new Object[]{}); Thread.sleep(2000); } } }
當我們運行上面Main程序過(guò)程中,我們動(dòng)態(tài)修改執行內容(SayHello中,從 hello zmj... 更改為 hello ping...),最終展示的內容如下:
hello zmj...
hello zmj...
hello zmj...
hello ping...
hello ping...
hello ping...
以上就是詳解Java類(lèi)動(dòng)態(tài)加載和熱替換的詳細內容,更多關(guān)于Java類(lèi)動(dòng)態(tài)加載和熱替換的資料請關(guān)注腳本之家其它相關(guān)文章!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站