Java應用程序運行時升級軟件,無需重新啓動的方式有兩種,熱部署和熱加載。
熱加載
熱加載即在在運行時重新加載class,實現原理主要依賴java的類加載機制,是在運行時通過重新加載改變類信息,直接改變程序行爲。在實現方式可以概括爲在容器啓動的時候起一條後臺線程,定時的檢測類文件的時間戳變化,如果類的時間戳變掉了,則將類重新載入。
生產環境中,由於熱加載這種直接修改jvm中字節碼的方式是難以監控的,不同於sql等執行可以記錄日誌,直接字節碼的修改幾乎無法記錄代碼邏輯的變化,對既有代碼行爲的影響難以控制,對於越注重安全的應用,熱加載帶來的風險越大,這好比給飛行中的飛機更換髮動機。
基於這種不安全性,在實際生產環境中應用很少。使用熱加載的應用有兩種:
1、需要頻繁部署的應用; 2、無法停止服務的應用
在生產中,並沒有需要頻繁部署的應用,即使是敏捷,再快也是一週一次的迭代,並且通過業務劃分和模塊化編程,部署的代價完全可以忽略不計,對於現有的應用,啓動耗時再長,也並非長到無法忍受,如果真的這麼長,那更應該考慮的是如何進行模塊拆分,分佈式部署了。
對於無法停止服務的應用,比如現在的雲計算平臺這樣分佈式應用,採用分批上線也可以滿足需求,類似熱部署方案應該是放在最後考慮的解決方案。
開發環境中,頻繁啓動應用卻隨處可見,熱加載可以顯著的提升工作效率,強烈推薦使用熱加載方式。(jrebel插件方式)
熱部署
熱部署就是在服務器運行時重新部署項目。原理與類加載類似,但它是直接重新加載整個應用,這種方式會釋放內存,比熱加載更加乾淨徹底,但同時也更費時間。
熱部署作爲一個比較靈活的機制,在實際的生產上運用還是有,尤其在雲計算中運用挺多。
體驗hotswap
以上均是理論派,下面玩點真格的。
我們知道對於Java應用程序來說,熱部署其實就是在運行時更新Java類文件。
java類的加載過程,即一個java類文件到虛擬機裏的對象,要經過如下過程。
首先通過java編譯器,將java文件編譯成class字節碼,類加載器讀取class字節碼,再將類轉化爲實例,對實例newInstance就可以生成對象。類加載器ClassLoader功能,也就是將class字節碼轉換到類的實例。
在java應用中,所有的實例都是由類加載器,加載而來。在類加載器中,java類只能被加載一次,並且無法卸載。那如何實現熱部署呢?我們可以直接自定義類加載器,並重寫ClassLoader的findClass方法。將java類卸載,並且替換更新版本的java類。
因此想要實現熱部署可以分以下三個步驟:
1、自定義ClassLoader
2、更新class類文件
3、創建新的ClassLoader去加載更新後的class類文件。
talk is cheap:
自定義ClassLoader
import java.io.IOException; import java.io.InputStream; /** * 自定義類加載器,並override findClass方法 * * */ public class MyClassLoader extends ClassLoader { @Override public Class<?> findClass(String name) throws ClassNotFoundException { try { String fileName = name.substring (name.lastIndexOf (".") + 1) + ".class"; InputStream is = this.getClass ( ).getResourceAsStream (fileName); byte[] b = new byte[is.available ( )]; is.read (b); return defineClass (name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException (name); } } }
更新class類文件
1. 準備你想替換的結果。
public class HelloWorld { public void say() { System.out.println ("Hello World V2"); } }
2. 編譯一下,得到class文件,存好。(我make後給它換了個名字還放在原文件夾,防止待會編譯後覆蓋沒了)
3. 修改HelloWorldJava 文件
public class HelloWorld { public void say() { System.out.println ("Hello World V1"); } }
4.編譯,得到新的class文件,
5.開始偷樑換柱
public class Hotswap { public static void main(String[] args) throws Exception { loadHelloWorld ( ); // 回收資源,釋放HelloWorld.class文件,使之可以被替換 System.gc ( ); Thread.sleep (1000);// 等待資源被回收 File fileV2 = new File ("/資料/技術學習/併發編程/out/production/併發編程/com/test/classloader/classLoaderTest/HelloWorld2.class"); File fileV1 = new File ("/資料/技術學習/併發編程/out/production/併發編程/com/test/classloader/classLoaderTest/HelloWorld.class"); fileV1.delete ( ); //刪除V1版本 fileV2.renameTo (fileV1); //更新V2版本 System.out.println ("Update success!"); loadHelloWorld ( ); } public static void loadHelloWorld() throws Exception { MyClassLoader myLoader = new MyClassLoader ( ); //自定義類加載器 Class<?> class1 = myLoader .findClass ("com.test.classloader.classLoaderTest.HelloWorld");//類實例 Object obj1 = class1.newInstance ( ); //生成新的對象 Method method = class1.getMethod ("say"); method.invoke (obj1); //執行方法say System.out.println (obj1.getClass ( )); //對象 System.out.println (obj1.getClass ( ).getClassLoader ( )); //對象的類加載器 } }
6. 結果:
存疑:
JRebel 插件實現熱部署時,運行時支持對新的類,final字段等的熱部署,而且效率很高,厲害了。