Arthas 熱部署實現原理


Arthas 是由阿里巴巴開源實現的一套 Java 診斷工具,能夠實現對 Java 進程全方位的診斷與調試。其豐富實用的命令選項使得其深受 Java 開發工程師的喜愛。

在 Arthas 中有一個命令可以實現部分代碼的熱部署功能,這里介紹一下它具體是怎么實現的。

熱部署概念

熱部署在近些年來在 Java 組成的應用十分受歡迎。主要源於隨着項目的越來越大,Java 程序的啟動需要加載大量的內容,導致啟動時間十分耗時。對於線上應用程序而言,相較於程序的運行時長,啟動時長幾乎可以忽略不計。而對於 Java 開發者來說,由於需要對代碼進行調試,頻繁的啟動應用所帶來的啟動耗時便成了負擔。這時候熱部署便應運而生了。

代碼的熱部署表示在在不重啟應用的情況下,只將代碼修改的部分替換到正在運行的程序中,從而實現動態代碼修改的效果。這樣就不需要開發者只改一行代碼也要重新啟動項目了。節省下來的重啟時間可以專注於業務代碼的開發過程,從而提升開發效率。


熱部署方案

目前市面上主流的熱部署解決方案有 JRebelHotSwapAgent等,如果開發者使用的是 Spirng Boot 的話可以試試 spring-boot-devtools。

今天要介紹的是 Arthas 中的熱部署命令redefine,對於熱部署解決方案,Arthas 使用了另一種解決方案實現,這里就要講下 JVMTI 了。


JVMTI 介紹

JVMTI(JVM Tool Interface)是 Java 虛擬機所提供的 native 編程接口,是 JVMPI(Java Virtual Machine Profiler Interface)和 JVMDI(Java Virtual Machine Debug Interface)的替代版本。

JVMTI可以用來開發並監控虛擬機,可以查看JVM內部的狀態,並控制JVM應用程序的執行。可實現的功能包括但不限於:調試、監控、線程分析、覆蓋率分析工具等。

Arthas 的熱部署的實現就是使用了 JVMTI 中的 Instrument 接口,通過該接口可以實現字節碼的動態替換。

Instrumention 支持的功能都在java.lang.instrument.Instrumentation接口中體現

image

public interface Instrumentation {
    //添加一個ClassFileTransformer
    //之后類加載時都會經過這個ClassFileTransformer轉換
    void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
    void addTransformer(ClassFileTransformer transformer);
    //移除ClassFileTransformer
    boolean removeTransformer(ClassFileTransformer transformer);
    boolean isRetransformClassesSupported();
    //將一些已經加載過的類重新拿出來經過注冊好的ClassFileTransformer轉換
    //retransformation可以修改方法體,但是不能變更方法簽名、增加和刪除方法/類的成員屬性
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
    // 判斷是否支持重新定義類
    boolean isRedefineClassesSupported();
    //重新定義某個類
    void redefineClasses(ClassDefinition... definitions) throws  ClassNotFoundException, UnmodifiableClassException;
    boolean isModifiableClass(Class<?> theClass);
    // 獲取全部已經加載的類
    @SuppressWarnings("rawtypes")
    Class[] getAllLoadedClasses();
    @SuppressWarnings("rawtypes")
    Class[] getInitiatedClasses(ClassLoader loader);
    // 獲取對象大小
    long getObjectSize(Object objectToSize);
    // 添加到啟動類加載器搜索路徑中
    void appendToBootstrapClassLoaderSearch(JarFile jarfile);
    // 添加到系統類夾雜器搜索路徑中
    void appendToSystemClassLoaderSearch(JarFile jarfile);
    boolean isNativeMethodPrefixSupported();
    void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);
}

關鍵詞 JVMTI

我們通過addTransformer方法注冊了一個ClassFileTransformer,后面類加載的時候都會經過這個Transformer處理。對於已加載過的類,可以調用retransformClasses來重新觸發這個Transformer的轉換。

redefineClasses 和 retransformClasses 的區別:

transform是對類的byte流進行讀取轉換的過程,需要先獲取類的byte流然后做修改。而redefineClasses更簡單粗暴一些,它需要直接給出新的類byte流,然后替換舊的。
transform可以添加很多個,retransformClasses 可以讓指定的類重新經過這些transform做轉換。

有了上面的工具,Arthas 就擁有了代碼熱部署的能力了,我們翻下 arthas 的源碼看看它是怎么做的:

// 以下代碼存在刪減,如需
// 創建一個新的熱部署模型
RedefineModel redefineModel = new RedefineModel();
// 獲取 JVMTI 中的 Instrumentation 對象,來執行 redefineClasses 實現熱部署
Instrumentation inst = process.session().getInstrumentation();
// 讀取文件路徑
// ...
List<ClassDefinition> definitions = new ArrayList<ClassDefinition>();
// Instrumentation inst 獲取全部加載的類
for (Class<?> clazz : inst.getAllLoadedClasses()) {
    if (bytesMap.containsKey(clazz.getName())) {
        // 如果包含則執行熱部署
        if (hashCode == null && classLoaderClass != null) {
            // 獲取匹配的類加載器
            List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
            if (matchedClassLoaders.size() == 1) {
                // 設置hashcode
                hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
            } else if (matchedClassLoaders.size() > 1) {
                // 如果匹配多個,則拋出異常
                //...
            } else {
                process.end(-1, "Can not find classloader by class name: " + classLoaderClass + ".");
                return;
            }
        }
        // 獲取對應的類加載器
        ClassLoader classLoader = clazz.getClassLoader();
        // 類定義列表
        definitions.add(new ClassDefinition(clazz, bytesMap.get(clazz.getName())));
        redefineModel.addRedefineClass(clazz.getName());
    }
}

try {
  // 獲取 JVMTI 中的 Instrumentation 對象,來執行 redefineClasses 實現熱部署
    inst.redefineClasses(definitions.toArray(new ClassDefinition[0]));
    process.appendResult(redefineModel);
    process.end();
} catch (Throwable e) {
    String message = "redefine error! " + e.toString();
    logger.error(message, e);
    process.end(-1, message);
}

Instrumentation 又是怎么實現熱部署的呢?通過翻閱 Instrumentation 的實現我們可以看到 redefineClasses 等方法的實現都是通過 JNI實現的,因此就需要我們去翻閱一下 JDK 源碼了。到目前為止,我們可以得到 Arthas 實現熱部署是通過 JDK 的工具類 Instrumentation實現的,通過傳入對應的類對象,和修改后的字節碼,便可以實現對目標類的字節碼進行調整替換了。


Instrumentation 通過調用 src\hotspot\share\prims\jvmtiRedefineClasses.cpp中的 VM_RedefineClasses 類,通過 虛擬機線程對目標類常量池中對應的符號進行了一一替換:

image


通過 Instrumentation實現熱部署的一些限制

並不是所有改動熱更新都將會成功,當前使用 Instrumentation#redefineClasses 還是存在一些限制。從源碼可以看到,我們只是對字節碼中已有的一些內容進行了替換,因此無法添加,刪除方法或字段,也不能更改方法的簽名或繼承關系。

參考資料

Java JVMTI和Instrumention機制介紹
open-jdk
RedefineCommand
手把手教你實現熱更新功能,帶你了解 Arthas 熱更新背后的原理
熱更新原理及實踐注意


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM