漫畫:Java如何實現熱更新?

Arthas(阿爾薩斯)是 Alibaba 開源的一款 Java 診斷工具,使用它我們可以監控和排查 Java 程序,然而它還提供了非常實用的 Java 熱更新功能。

所謂的 Java 熱更新是指在不重啓項目的情況下實現代碼的更新與替換。使用它可以實現不停機更新 Java 程序,尤其是對那些啓動非常耗時的 Java 項目來說,更是效果顯著。

Arthas 的使用其實非常簡單,它爲我們提供了一個 Jar 包,我們只需要把這個 Jar 下載到本地,然後運行這個 Jar 包就可以正常使用它的功能了。

Arthas 功能簡述

當你遇到以下類似問題而束手無策時,Arthas 可以幫助你解決(來自官方):

  1. 這個類從哪個 jar 包加載的?爲什麼會報各種類相關的 Exception?

  2. 我改的代碼爲什麼沒有執行到?難道是我沒 commit?分支搞錯了?

  3. 遇到問題無法在線上 debug,難道只能通過加日誌再重新發布嗎?

  4. 線上遇到某個用戶的數據處理有問題,但線上同樣無法 debug,線下無法重現!

  5. 是否有一個全局視角來查看系統的運行狀況?

  6. 有什麼辦法可以監控到JVM的實時運行狀態?

  7. 怎麼快速定位應用的熱點,生成火焰圖?

Arthas 支持 JDK 6+,支持 Linux/Mac/Winodws,它採用命令行交互模式,同時提供豐富的 Tab 自動補全功能,進一步方便進行問題的定位和診斷。

Arthas 使用

Arthas 的使用步驟如下。

步驟一:下載 Arthas

首先,我們先把 Arthas 的 Jar 包下載到本地,它的下載地址是:https://alibaba.github.io/arthas/arthas-boot.jar

步驟二:啓動 Arthas

我們只需要使用普通的 jar 包啓動命令:java -jar arthas-boot.jar 來啓動 Arthas 即可,啓動成功之後的運行界面如下:


如上圖所示則表示 Arthas 啓動成功。

小貼士:當我們運行 java -jar arthas-boot.jar 命令時,首先需要先切換目錄至該 jar 包的位置,才能正常的啓動 Arthas。

步驟三:運行 Arthas

當我們啓動完 Arthas 之後,根據上圖的提示,我們需要選擇一個要調試的 Java 進程,例如我們輸入“4”來監測我自己寫的一個 Java 測試程序,執行結果如下:


當出現 Arthas 的 logo 之後,表示 Arthas 正常加載了 Java 進程。

步驟四:操作 Arthas

當 Arthas 加載 Java 進程成功之後,我們就可以輸入相關的命令來查看相關的信息了。

假如我們把本地環境視爲生產服務器,我們此時需要查看某個運行的 Java 程序是否爲最新版的。

在沒有 Arthas 之前,我們通常的步驟是這樣的:

  1. 找到相應的 jar 包(或者 war 包);

  2. 將 jar 包(或者 war 包)下載到本地;

  3. 找出相應的類進行解壓操作;

  4. 然後將解壓的 class 文件拖拽到 Java 編譯器(Idea 或 Eclipse)中,查看是否爲最新的代碼。

但如果使用的是 Arthas,那麼我們就可以直接通過反編譯命令,將字節碼編譯爲正常的 Java 代碼,然後再確認是否爲最新的代碼即可。我們只需要執行 jad 命令即可,實現示例如下:


這樣我們就可以直接來查看這個發佈的程序是否爲最新版本了。

不僅如此,我們還可以使用 Arthas 來監測整個程序的運行情況,如下圖所示:

我們還可以用 Arthas 來查看一些 JVM 的相關信息,如下圖所示:


更多 Arthas 的功能,請訪問:https://alibaba.github.io/arthas/commands.html

熱更新 Java 代碼

假如我們原來的代碼是這樣的:

package com.example;

import java.util.concurrent.TimeUnit;

public class App {
    public static void main(String[] args) throws InterruptedException {
        while (true) { // 每兩秒鐘打印一條信息
            TimeUnit.SECONDS.sleep(3);
            sayHi();
        }
    }

    private static void sayHi() {
        // 需要修改的標識
        boolean flag = true;
        if (flag) {
            System.out.println("Hello,Java.");
        } else {
            System.out.println("Hello,Java中文社羣.");
        }
    }
}

我們現在想要把 flag 變量改爲 false 就可以這樣來做:

  1. 使用 Arthas 的內存編譯工具將新的 Java 代碼編譯爲字節碼;

  2. 使用 Arthas 的 redefine 命令實現熱更新。

1.編譯字節碼

首先,我們需要將新的 Java 代碼編譯爲字節碼,我們可以通過 Arthas 提供的 mc 命令實現,mc 是 Memory Compiler(內存編譯器)的縮寫。

實現示例如下:

[arthas@3478]$ mc /Users/admin/Desktop/App.java -d /Users/admin/Desktop
Memory compiler output:
/Users/admin/Desktop/com/example/App.class
Affect(row-cnt:1) cost in 390 ms.

其中 -d 表示編譯文件的存放位置。

小貼士:我們也可以使用 javac App.java 生成的字節碼,它與此步驟執行的結果相同。

2.執行熱更新

有了字節碼文件之後,我們就可以使用 redefine 命令來實現熱更新了,實現示例如下:

[arthas@51787]$ redefine /Users/admin/Desktop/com/example/App.class
redefine success, size: 1

從上述結果可以看出,熱更新執行成功,此時我們去控制檯查看執行結果,如下圖所示:

這說明熱更新執行確實成功了。

Arthas 熱更新注意事項

使用熱更新功能有一些條件限制,我們只能用它來修改方法內部的一些業務代碼,如果我們出現了以下任意一種情況,那麼熱更新就會執行失敗:

  1. 增加類屬性(類字段);

  2. 增加或刪除方法;

  3. 替換正在運行的方法。

最後一條我們需要單獨說明一下,假如我們把上面的示例改爲如下代碼:

package com.example;

import java.util.concurrent.TimeUnit;

public class App {
    public static void main(String[] args) throws InterruptedException {
        while (true) { // 每兩秒鐘打印一條信息
            TimeUnit.SECONDS.sleep(3);
            boolean flag = true;
            if (flag) {
                System.out.println("Hello,Java.");
            } else {
                System.out.println("Hello,Java中文社羣.");
            }
        }
    }
}

那麼此時我們再進行熱更新操作修改 flag 的值,那麼就會執行失敗,因爲我們替換的是正在運行中的方法,而我們正常示例中的代碼之所以能成功,是因爲我們在 while 無線循環中調用了另一個方法,而那個方法是被間歇性使用的,因此可以替換成功。

總結

本文我們講了 Arthas 的概念以及具體的使用流程,Arthas 其實就是一個普通的 Java 程序,我們可以使用 java -jar arthas-boot.jar 來啓動它,然後再選擇我們要操作的 Java 進程,這樣就可以實現狀態監控和其他操作。

文章的後半部分,我們介紹了 Arthas 的熱更新功能,而熱更新本質上只需要使用一個 redefine 命令來加載新的字節碼文件就可以實現熱更新了,但需要注意熱更新不能替換正在運行的方法,它只能修改方法內部的業務代碼,如果修改了類字段或者是更改了類方法,那麼熱更新就會執行失敗。

最後的話原創不易,斥資找人畫了漫畫,看着老王認真的份上,點個「在看」再走唄,這是對我最大的支持與鼓勵,謝謝!

往期推薦

我寫了10年博客,卻被人說“不火”?我是這樣懟回去的?

鏈表竟然比數組慢了1000多倍?(動圖+性能評測)

關注下方二維碼,每一天都有乾貨!

點亮“在看”,助我寫出更多好文!

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