前言
本文是我介紹 Arthas 系列文章的第一篇。java
通常線上問題比開發環境的問題更難解決,一個主要的緣由便在於開發態能夠任意 debug 斷點調試,而線上環境通常不容許遠程調試,因此在實踐中,我通常習慣用 Arthas 來定位線上的問題。安全
Arthas 是阿里巴巴開源的 Java 應用診斷利器服務器
Arthas 能夠完成不少騷操做,今天給你們介紹的 Arthas 診斷技巧即是 -- 熱更新線上代碼。在生產環境熱更新代碼,並非很好的行爲,可能會引起一些問題微信
黑屏化的操做可能會致使誤操做併發
不符合安全生產的規範,不知足可監控、可回滾、可降級編輯器
但有時候也有一些場景能夠考慮使用 Arthas 來熱更,例如開發環境沒法復現的問題、找到修復思路後臨時驗證等。函數
本文以 Arthas 3.1.7 版本爲例,主要使用到 jad
/mc
/redefine
三個指令。spa
示例
在 arthas-demo 示例中,一共有兩個類,一個 HelloService 類,sayHello 方法負責不斷的打印 hello world
:.net
public class HelloService {
public void sayHello() {
System.out.println("hello world");
}
}
HelloService 用於模擬咱們平常開發的一些業務 Service,另外還有一個 Main 函數,負責啓動進程,並循環調用debug
public class Main {
public static void main(String[] args) throws InterruptedException {
HelloService helloService = new HelloService();
while (true) {
Thread.sleep(1000);
helloService.sayHello();
}
}
}
需求
假設這段代碼運行在線上,咱們但願經過 Arthas 將 hello world
的輸出更改成 hello arthas
。
Arthas 修改熱更的邏輯主要分爲三步:
jad 命令反編譯出內存中的字節碼,生成 class 文件
修改代碼,使用 mc 命令內存編譯新的 class 文件
redefine 從新加載新的 class 文件
從而達到熱更新的效果
jad 反編譯
當掛載上 Arthas 以後,執行
$ jad --source-only moe.cnkirito.arthas.demo.HelloService > /tmp/HelloService.java
將字節碼文件輸出到指定的位置,查看其內容,與示例中的源碼內容一致:
/*
* Decompiled with CFR.
*/
package moe.cnkirito.arthas.demo;
import java.io.PrintStream;
public class HelloService {
public void sayHello() {
System.out.println("hello world");
}
}
命令中 --source-only
的含義爲,只輸出源碼部分,若是不加這個參數,在反編譯出的內容頭部會攜帶類加載器的信息:
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@20d5ad12
Location:
/Users/xujingfeng/IdeaProjects/arthas-demo/target/classes/
在服務器上能夠直接使用 vi 等編輯器對源碼進行編輯。將 hello world
改成 hello arthas
,爲下一步作準備。
sc 查找類加載器
mc 命令編譯文件須要傳入該類對應類加載器的 hash 值,須要先使用 sc 命令查看 HelloService 的累加器信息
$ sc -d moe.cnkirito.arthas.demo.HelloService
輸出:
class-info moe.cnkirito.arthas.demo.HelloService
code-source /Users/xujingfeng/IdeaProjects/arthas-demo/target/classes/
name moe.cnkirito.arthas.demo.HelloService
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name HelloService
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@20d5ad12
classLoaderHash 18b4aac2
最後一行 classLoaderHash
即爲 HelloService 的類加載器 hash 值。
Arthas 支持 grep,你也能夠簡化該操做爲:
sc -d moe.cnkirito.arthas.demo.HelloService | grep classLoaderHash
mc 內存編譯
$ mc -c 18b4aac2 /tmp/HelloService.java -d /tmp
Memory compiler output:
/tmp/moe/cnkirito/arthas/demo/HelloService.class
使用 -c
指定類加載器的 hash 值。編譯完成後,/tmp 目錄下會生成對應的 class 字節碼文件
redefine 熱更新代碼
$ redefine /tmp/moe/cnkirito/arthas/demo/HelloService.class
檢查結果
hello world
hello world
hello world
hello world
hello arthas
hello arthas
hello arthas
hello arthas
熱更新成功
常見問題
redefine 使用限制
不容許新增或者刪除 field/method
會出現相似下面的提示
redefine error! java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
運行中的方法不會馬上生效,會在下一次進入該方法時才能生效。
很好理解,併發問題
mc 常見問題
mc 命令有可能失敗
由於運行時環境和編譯時環境的 JDK 可能有版本差別,mc 可能會失敗。若是編譯失敗能夠在本地編譯好
.class
文件,再上傳到服務器當存在內部類時,一次會生成多個 class 文件
public class HelloService {
public void sayHello() {
Inner.test();
}
public static class Inner {
public static void test() {
System.out.println("hello inner");
}
}
}執行 mc
$ mc -c 18b4aac2 /tmp/HelloService.java -d /tmp
Memory compiler output:
/tmp/moe/cnkirito/arthas/demo/HelloService$Inner.class
/tmp/moe/cnkirito/arthas/demo/HelloService.class注意 redefine 時也能夠同時傳入多個入參
$ redefine /tmp/moe/cnkirito/arthas/demo/HelloService$Inner.class /tmp/moe/cnkirito/arthas/demo/HelloService.class
redefine success, size: 2
參考文章
https://blog.csdn.net/hengyunabc/article/details/87718469
Arthas 交流釘釘羣
羣號:21965291
本文分享自微信公衆號 - Kirito的技術分享(cnkirito)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。