共計 9143 個字符,預計需要花費 23 分鐘才能閱讀完成。
這篇文章將為大家詳細講解有關 Java 線上問題排查工具 Arthas 原理以及用法是什么,文章內容質量較高,因此丸趣 TV 小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
前言
當你興沖沖地開始運行自己的 Java 項目時,你是否遇到過如下問題:
程序在穩定運行了,可是實現的功能點了沒反應。
為了修復 Bug 而上線的新版本,上線后發現 Bug 依然在,卻想不通哪里有問題?
想到可能出現問題的地方,卻發現那里沒打日志,沒法在運行中看到問題,只能加了日志輸出重新打包——部署——上線
程序功能正常了,可是為啥響應時間這么慢,在哪里出現了問題?
程序不但穩定運行,而且功能完美,但跑了幾天或者幾周過后,發現響應速度變慢了,是不是內存泄漏了?
以前,你碰到這些問題,解決的辦法大多是,修改代碼,重新上線。但是在大公司里,上線的流程是非常繁瑣的,如果為了多加一行日志而重新發布版本,無疑是非常折騰人的。
現在,我們有了更為優雅的線上調試方法 – 來自阿里巴巴開源的 Arthas。
下圖是 Arthas 文檔中對于為什么要使用它的描述,我進行了精簡:
線上 Debug 神器 ArthasArthas 使用實例
命令的詳細文檔請參考:alibaba.github.io/arthas/comm…
快速啟動
快速啟動它,你只需要兩行命令:
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
隨后,在界面出現的進程中,選擇你的程序序號,比如 1
這樣你就進入了 arthas 的控制臺。
基本使用
Arthas 有如下功能:
1. 首先是我認為的“上帝視角”指令:Dashboard
當前系統的實時數據面板,按 ctrl+c 退出;
當運行在 Ali-tomcat 時,會顯示當前 tomcat 的實時信息,如 HTTP 請求的 qps, rt, 錯誤數, 線程池信息等等。
通過這些,你可以對于整個程序進程有個直觀的數據監控。
2. 類加載問題相關指令
SC:查看 JVM 已加載的類信息
通過 SC 我們可以看到我們這個類的詳細信息,包括是從哪個 jar 包讀取的,他是不是接口 / 枚舉類等,甚至包括他是從哪個類加載器加載的。
上圖中代碼:
[arthas@37]$ sc -d *MathGame
class-info demo.MathGame
code-source /home/scrapbook/tutorial/arthas-demo.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@70dea4e
+-sun.misc.Launcher$ExtClassLoader@69260973
classLoaderHash 70dea4e
SC 也可以查看已加載的類,幫助你看是否有沒有納入進來的類,尤其是在 Spring 中,可以判斷的你的依賴有沒有正確的進來。
上圖中代碼:
# 查看 JVM 已加載的類信息
[arthas@37]$ sc javax.servlet.Filter
com.example.demo.arthas.AdminFilterConfig$AdminFilter
javax.servlet.Filter
org.apache.tomcat.websocket.server.WsFilter
org.springframework.boot.web.filter.OrderedCharacterEncodingFilter
org.springframework.boot.web.filter.OrderedHiddenHttpMethodFilter
org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter
org.springframework.boot.web.filter.OrderedRequestContextFilter
org.springframework.web.filter.CharacterEncodingFilter
org.springframework.web.filter.GenericFilterBean
org.springframework.web.filter.HiddenHttpMethodFilter
org.springframework.web.filter.HttpPutFormContentFilter
org.springframework.web.filter.OncePerRequestFilter
org.springframework.web.filter.RequestContextFilter
org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
Affect(row-cnt:14) cost in 11 ms.
# 查看已加載類的方法信息
[arthas@37]$ sm java.math.RoundingMode
java.math.RoundingMode init (Ljava/lang/String;II)V
java.math.RoundingMode values()[Ljava/math/RoundingMode;
java.math.RoundingMode valueOf(I)Ljava/math/RoundingMode;
java.math.RoundingMode valueOf(Ljava/lang/String;)Ljava/math/RoundingMode;
Affect(row-cnt:4) cost in 6 ms.
jad:反編譯某個類,或者反編譯某個類的某個方法。
上圖中代碼:
# 反編譯只顯示源碼
jad --source-only com.Arthas
# 反編譯某個類的某個方法
jad --source-only com.Arthas mysql
[arthas@37]$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@70dea4e
+-sun.misc.Launcher$ExtClassLoader@69260973
Location:
/home/scrapbook/tutorial/arthas-demo.jar
* Decompiled with CFR.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class MathGame { private static Random random = new Random();
public int illegalArgumentCount = 0;
public List Integer primeFactors(int number) { if (number 2) {
++this.illegalArgumentCount;
throw new IllegalArgumentException( number is: + number + , need = 2
}
ArrayList Integer result = new ArrayList Integer
int i = 2;
while (i = number) { if (number % i == 0) { result.add(i);
number /= i;
i = 2;
continue;
}
++i;
}
return result;
}
public static void main(String[] args) throws InterruptedException { MathGame game = new MathGame();
do { game.run();
TimeUnit.SECONDS.sleep(1L);
} while (true);
}
public void run() throws InterruptedException {
try { int number = random.nextInt() / 10000;
List Integer primeFactors = this.primeFactors(number);
MathGame.print(number, primeFactors);
}
catch (Exception e) { System.out.println(String.format( illegalArgumentCount:%3d, , this.illegalArgumentCount) + e.getMessage());
}
}
public static void print(int number, List Integer primeFactors) {
StringBuffer sb = new StringBuffer(number + =
for (int factor : primeFactors) { sb.append(factor).append( *
}
if (sb.charAt(sb.length() - 1) == * ) { sb.deleteCharAt(sb.length() - 1);
}
System.out.println(sb);
}
Affect(row-cnt:1) cost in 760 ms.
3. 方法運行相關指令
watch:方法執行的數據觀測
你可以通過 watch 指令,來監控某個類,監控后,運行下你的功能,復現下場景,arthas 會提供給你具體的出參和入參,幫助你排查故障。
trace:輸出方法調用路徑,并輸出耗時
這個指令對于優化代碼非常的有用,可以看出具體每個方法執行的時間,如果是 for 循環等重復語句,還能看出 n 次循環中的最大耗時,最小耗時,和平均耗時,完美!
tt:官方名為時空隧道
這是我調試用的最多的指令,在你對某方法開啟 tt 后,會記錄下每一次的調用(你需要設置最大監控次數),然后你可以在任何時候會看這里面的調用,包括出參,入參,運行耗時,是否異常等。非常強大。
4. 線程調試相關指令
thread 相關命令:
thread -n:排列出 CPU 使用率 Top N 的線程。
thread -b:排查阻塞的線程
我們代碼有時候設計的不好,會引發死鎖的問題,卡住整個線程執行,使用這個指令可以輕松的找到問題線程,以及問題的執行語句。
5. 強大的 ognl 表達式
眾所周知,一般來說,表達式都是調試工具里最強的指令,哈哈。
在 Arthas 中你可以利用 ognl 表達式語言做很多事,比如執行某個方法,獲取某個信息,甚至進行修改。
[arthas@19856]$ ognl @com.Arthas@hashSet
@HashSet[ @String[count1],
@String[count2],
@String[count29],
@String[count28],
@String[count0],
@String[count27],
@String[count5],
@String[count26],
@String[count6],
@String[count25],
@String[count3],
@String[count24],
[arthas@19856]$ ognl @com.Arthas@hashSet.add(test)
@Boolean[true]
[arthas@19856]$
# 查看添加的字符
[arthas@19856]$ ognl @com.Arthas@hashSet | grep test
@String[test],
[arthas@19856]$
甚至你可以動態更換日志輸出級別。
$ ognl @com.lz.test@LOGGER.logger.privateConfig
@PrivateConfig[ loggerConfig=@LoggerConfig[root],
loggerConfigLevel=@Level[INFO],
intLevel=@Integer[400],
$ ognl @com.lz.test@LOGGER.logger.setLevel(@org.apache.logging.log4j.Level@ERROR)
$ ognl @com.lz.test@LOGGER.logger.privateConfig
@PrivateConfig[ loggerConfig=@LoggerConfig[root],
loggerConfigLevel=@Level[ERROR],
intLevel=@Integer[200],
]
使用 Arthas 解決具體問題 1. 響應時間異常問題
工作中遇到一個優化問題,系統中一個導出表格的功能,響應時間長達 2 分鐘,雖然給內部使用,但也不能這么夸張,用 trace 跟蹤下方法,發現是其中的手機號加解密函數占用了非常大的時間,幾千個手機號,進行了解密后加密的精彩操作,最終導致了兩分鐘的返回時間。
2. 某功能 Bug 導致服務器返回 500
首先通過 trace 看異常報錯的方法,之后通過 tt 排查方法,發現入參進來后,居然走錯了方法(因為多態),走到了返回 null 的方法中,所以導致了 NPE 空指針錯誤。
補充
Arthas 還支持 Web Console,詳見:alibaba.github.io/arthas/web-…
相似工具
BTrace 一是個歷史比較久的工具,觀察下來 Arthas 其實和它的理念蠻相似的,相信 Arthas 也參考過 Btrace,作為一個學習樣例來開發 Arthas。詳細的優劣勢看圖:
其他的相似工具,還有 jvm-sandbox,有興趣的朋友可以去看看。
原理淺談
分為三個部分:
啟動
arthas 服務端代碼分析
arthas 客戶端代碼分析
啟動
使用了阿里開源的組件 cli, 對參數進行了解析:com.taobao.arthas.boot.Bootstrap
在傳入參數中沒有 pid, 則會調用本地 jps 命令, 列出 java 進程。
進入主邏輯, 會在用戶目錄下建立 .arthas 目錄, 同時下載 arthas-core 和 arthas-agent 等 lib 文件, 最后啟動客戶端和服務端。
通過反射的方式來啟動字符客戶端。
服務端——前置準備
看服務端啟動命令可以知道 從 arthas-core.jar 開始啟動,arthas-core 的 pom.xml 文件里面指定了 mainClass 為 com.taobao.arthas.core.Arthas,使得程序啟動的時候從該類的 main 方法開始運行。
首先解析入參,生成 com.taobao.arthas.core.config.Configure 類,包含了相關配置信息;
使用 jdk-tools 里面的 VirtualMachine.loadAgent,其中第一個參數為 agent 路徑,第二個參數向 jar 包中的 agentmain() 方法傳遞參數(此處為 agent-core.jar 包路徑和 config 序列化之后的字符串),加載 arthas-agent.jar 包;
運行 arthas-agent.jar 包,指定了 Agent-Class 為 com.taobao.arthas.agent.AgentBootstrap。
上圖中代碼:
public class Arthas { private Arthas(String[] args) throws Exception { attachAgent(parse(args));
}
private Configure parse(String[] args) {
// 省略非關鍵代碼,解析啟動參數作為配置,并填充到 configure 對象里面
return configure;
}
private void attachAgent(Configure configure) throws Exception {
// 省略非關鍵代碼,attach 到目標進程
virtualMachine = VirtualMachine.attach( + configure.getJavaPid());
virtualMachine.loadAgent(configure.getArthasAgent(),
configure.getArthasCore() + + configure.toString());
}
public static void main(String[] args) { new Arthas(args);
}
}
服務端——監聽客戶端請求
如果是 exit,logout,quit,jobs,fg,bg,kill 等直接執行;
如果是其他的命令,則創建 Job,并運行;
創建 Job 時,會根據具體客戶端傳遞的命令,找到對應的 Command,并包裝成 Process, Process 再被包裝成 Job;
運行 Job 時,反向先調用 Process,再找到對應的 Command,最終調用 Command 的 process 處理請求。
服務端——Command 處理流程
不需要使用字節碼增強的命令
其中 JVM 相關的使用 java.lang.management 提供的管理接口,來查看具體的運行時數據。比較簡單,就不介紹了。
需要使用字節碼增強的命令
字節碼增加的命令統一繼承 EnhancerCommand 類,process 方法里面調用 enhance 方法進行增強。調用 Enhancer 類 enhance 方法,該方法內部調用 inst.addTransformer 方法添加自定義的 ClassFileTransformer,這邊是 Enhancer 類。
Enhancer 類使用 AdviceWeaver(繼承 ClassVisitor),用來修改類的字節碼。重寫了 visitMethod 方法,在該方法里面修改類指定的方法。visitMethod 方法里面使用了 AdviceAdapter(繼承了 MethodVisitor 類),在 onMethodEnter 方法, onMethodExit 方法中,把 Spy 類對應的方法(ON_BEFORE_METHOD,ON_RETURN_METHOD,ON_THROWS_METHOD 等)編織到目標類的方法對應的位置。
在前面 Spy 初始化的時候可以看到,這幾個方法其實指向的是 AdviceWeaver 類的 methodOnBegin,methodOnReturnEnd 等。在這些方法里面都會根據 adviceId 查找對應的 AdviceListener,并調用 AdviceListener 的對應的方法,比如 before,afterReturning, afterThrowing。
客戶端
客戶端代碼在 arthas-client 模塊里面,入口類是 com.taobao.arthas.client.TelnetConsole。
主要使用 apache commons-net jar 進行 telnet 連接,關鍵的代碼有下面幾步:
構造 TelnetClient 對象,并初始化
構造 ConsoleReader 對象,并初始化
調用 IOUtil.readWrite(telnet.getInputStream(), telnet.getOutputStream(), System.in, consoleReader.getOutput()) 處理各個流,一共有四個流:
telnet.getInputStream()
telnet.getOutputStream()
System.in
consoleReader.getOutput()
請求時:從本地 System.in 讀取,發送到 telnet.getOutputStream(),即發送給遠程服務端。響應時:從 telnet.getInputStream() 讀取遠程服務端發送過來的響應,并傳遞給 consoleReader.getOutput(),即在本地控制臺輸出。
關于源碼,深入下去還有很多東西需要生啃,我也沒有消化得很好,大家可以繼續閱讀詳細資料。
總結
Arthas 是一個線上 Debug 神器,小白也可以輕松上手。
一鍵安裝并啟動 Arthas
方式一:通過 Cloud Toolkit 實現 Arthas 一鍵遠程診斷
Cloud Toolkit 是阿里云發布的免費本地 IDE 插件,幫助開發者更高效地開發、測試、診斷并部署應用。通過插件,可以將本地應用一鍵部署到任意服務器,甚至云端(ECS、EDAS、ACK、ACR 和 小程序云等);并且還內置了 Arthas 診斷、Dubbo 工具、Terminal 終端、文件上傳、函數計算 和 MySQL 執行器等工具。不僅僅有 IntelliJ IDEA 主流版本,還有 Eclipse、Pycharm、Maven 等其他版本。
推薦使用 IDEA 插件下載 Cloud Toolkit 來使用 Arthas:http://t.tb.cn/2A5CbHWveOXzI7sFakaCw8
方式二:直接下載
地址:
https://github.com/alibaba/arthas。
關于 Java 線上問題排查工具 Arthas 原理以及用法是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。