熱加載、熱部署,體驗hotswap

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字段等的熱部署,而且效率很高,厲害了。

 

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章