一、概述
Arthas是一款阿里巴巴開源的 Java 線上診斷工具,功能非常強大,可以解決很多線上不方便解決的問題。
Arthas診斷使用的是命令行交互模式,支持JDK6+,Linux、Mac、Windows 操作系統(tǒng),命令還支持使用?tab
?鍵對各種信息的自動補全,診斷起來非常利索。
官網(wǎng):https://arthas.aliyun.com/
Arthas(阿爾薩斯)能為你做什么?
Arthas 是 Alibaba 開源的 Java 診斷工具,深受開發(fā)者喜愛。
當(dāng)你遇到以下類似問題而束手無策時,Arthas可以幫助你解決:
這個類從哪個 jar 包加載的?為什么會報各種類相關(guān)的 Exception?
我改的代碼為什么沒有執(zhí)行到?難道是我沒 commit?分支搞錯了?
遇到問題無法在線上 debug,難道只能通過加日志再重新發(fā)布嗎?
線上遇到某個用戶的數(shù)據(jù)處理有問題,但線上同樣無法 debug,線下無法重現(xiàn)!
是否有一個全局視角來查看系統(tǒng)的運行狀況?
有什么辦法可以監(jiān)控到 JVM 的實時運行狀態(tài)?
怎么快速定位應(yīng)用的熱點,生成火焰圖?
怎樣直接從 JVM 內(nèi)查找某個類的實例?
Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同時提供豐富的 Tab 自動補全功能,進一步方便進行問題的定位和診斷。
?
二、下載安裝
官方推薦使用?arthas-boot
?進行安裝,非常方便,以下是基于 Linux 系統(tǒng)環(huán)境進行演示,一般解決線上問題也是基于 Linux 環(huán)境。
第一步:下載
在任何目錄下載?arthas-boot
?這個包。
wget https://alibaba.github.io/arthas/arthas-boot.jar
第二步:啟動
java -jar arthas-boot.jar --target-ip 0.0.0.0
?
默認情況下, arthas server 偵聽的是?127.0.0.1
?這個IP,如果希望遠程可以訪問,可以使用--target-ip
?的參數(shù)
arthas-boot
?是?Arthas
?的啟動程序,它啟動后,會列出所有的 Java 進程,輸入需要診斷的目標(biāo)進程序號即可。
Arthas支持通過 Web Socket來連接。
當(dāng)在本地啟動時,可以訪問?http://127.0.0.1:8563/
?,通過瀏覽器來使用 Arthas。
輸入?help
?可以看到常用的幫助命令:
arthas-boot.jar
?支持很多參數(shù),可以執(zhí)行?java -jar arthas-boot.jar -h
?查看
第三步:選擇進程
運行?arthas-boot
?后,控制臺會顯示所有 Java 進程,選擇一個你需要診斷的進程。
如第二步所示,這里有只有一個 Java 進程,輸入序號1,回車,Arthas會附到目標(biāo)進程上,并輸出日志:
[root@ofp-ofppricefeed-app-6ffbf6fc85-vz8qx arthas]# java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.5.0
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 27 org.apache.catalina.startup.Bootstrap
?至此安裝啟動完成
三、使用
啟動完成后,當(dāng)前光標(biāo)會進入?arthas
?的控制臺,接受各種操作命令。
dashboard
輸入 dashboard 顯示當(dāng)前系統(tǒng)的實時數(shù)據(jù)面板,按 ctrl+c 即可退出。
數(shù)據(jù)說明:
- ID: Java級別的線程ID,注意這個ID不能跟jstack中的nativeID一一對應(yīng)
- NAME: 線程名
- GROUP: 線程組名
- PRIORITY: 線程優(yōu)先級, 1~10之間的數(shù)字,越大表示優(yōu)先級越高
- STATE: 線程的狀態(tài)
- CPU%: 線程消耗的cpu占比,采樣100ms,將所有線程在這100ms內(nèi)的cpu使用量求和,再算出每個線程的cpu使用占比。
- TIME: 線程運行總時間,數(shù)據(jù)格式為分:秒
- INTERRUPTED: 線程當(dāng)前的中斷位狀態(tài)
- DAEMON: 是否是daemon線程
?
thread
查看當(dāng)前 JVM 的線程堆棧信息。
- **thread **查詢?nèi)烤€程
- thread -n 5 打印前5個最忙的線程并打印堆棧
- thread -all 顯示所有匹配的線程
- thread -n 3 -i 1000 列出1000ms內(nèi)最忙的3個線程棧
- thread –state WAITING 查看指定狀態(tài)的線程,(TIMED_WAITI、WAITING、RUNNABLE等等)
- thread -b 找出阻塞其他線程的線程,當(dāng)出現(xiàn)死鎖后,會提示你出現(xiàn)死鎖的位置
?
sc
查看 JVM 已加載的類詳細信息。
Search-Class
?的簡寫,這個命令能搜索出所有已經(jīng)加載到 JVM 中的 Class 信息,sc -d *MathGame
如果搜索的是接口,還會搜索所有的實現(xiàn)類。比如查看所有的?Filter
?實現(xiàn)類:
sc javax.servlet.Filter
-d
?參數(shù)可以打印出類加載的具體信息,方便定位類加載問題
sc
?支持通配符,比如搜索所有的?StringUtils
:
sc *StringUtils
打印類的?Field
?信息:
sc -d -f demo.MathGame
sm
查看已加載類的方法信息
Search-Method
?的簡寫,這個命令能搜索出所有已經(jīng)加載了 Class 信息的方法信息。
sm 命令只能看到由當(dāng)前類所聲明 (declaring) 的方法,父類則無法看到
查看 String 類的全部方法:
sm java.lang.String
查看具體方法的信息:
sm java.lang.String toString
通過?-d
?參數(shù)可以打印函數(shù)的具體屬性,展示每個方法的詳細信息():
sm -d java.math.RoundingMode
jad
反編譯指定已加載類的源代碼
jad demo.MatthGame
- jad demo.MathGame?將內(nèi)存加載的class反編成代碼和classloader信息(注:不是源代碼)
- **jad --source-only demo.MathGame **將內(nèi)存加載的class反編譯成java代碼
trace
方法內(nèi)部調(diào)用路徑,并輸出方法路徑上的每個節(jié)點上耗時
package demo;
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();
private int illegalArgumentCount = 0;
public List<Integer> primeFactors(int number) {
/*44*/ if (number < 2) {
/*45*/ ++this.illegalArgumentCount;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");
}
ArrayList<Integer> result = new ArrayList<Integer>();
/*50*/ int i = 2;
/*51*/ while (i <= number) {
/*52*/ if (number % i == 0) {
/*53*/ result.add(i);
/*54*/ number /= i;
/*55*/ i = 2;
continue;
}
/*57*/ ++i;
}
/*61*/ return result;
}
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
while (true) {
/*16*/ game.run();
/*17*/ TimeUnit.SECONDS.sleep(1L);
}
}
public void run() throws InterruptedException {
try {
/*23*/ int number = random.nextInt() / 10000;
/*24*/ List<Integer> primeFactors = this.primeFactors(number);
/*25*/ MathGame.print(number, primeFactors);
}
catch (Exception e) {
/*28*/ 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 + "=");
/*34*/ for (int factor : primeFactors) {
/*35*/ sb.append(factor).append('*');
}
/*37*/ if (sb.charAt(sb.length() - 1) == '*') {
/*38*/ sb.deleteCharAt(sb.length() - 1);
}
/*40*/ System.out.println(sb);
}
}
- trace demo.MathGame run (監(jiān)控追蹤MathGame類下面的run方法)?
?
- trace demo.MathGame run -n 1 (監(jiān)控追蹤MathGame類下面的run方法,只監(jiān)控一次)
- trace --skipJDKMethod false demo.MathGame run -n 1 (監(jiān)控run所有方法,因為trace默認不追蹤jdk自帶的方法–skipJDKMethod false 就是開啟追蹤jdk默認的方法)
?
- trace demo.MathGame run ‘#cost > 0.1’ (查詢大于0.1ms的執(zhí)行方法)
?
?
stack
查詢某個方法被執(zhí)行的時候的調(diào)用路徑
- stack demo.MathGame primeFactors (查詢primeFactors被調(diào)用的時候調(diào)用的引用順序)
- stack demo.MathGame primeFactors ‘params[0]<0’ -n 2(顯示入?yún)?的引用路徑,顯示兩次)
tt
方法執(zhí)行數(shù)據(jù)的時空隧道,記錄下指定方法每次調(diào)用的入?yún)⒑头祷匦畔ⅲ⒛軐@些不同的時間下調(diào)用進行觀測。
watch 雖然很方便和靈活,但需要提前想清楚觀察表達式的拼寫,這對排查問題而言要求太高,因為很多時候我們并不清楚問題出自于何方,只能靠蛛絲馬跡進行猜測。這個時候如果能記錄下當(dāng)時方法調(diào)用的所有入?yún)⒑头祷刂?、拋出的異常會對整個問題的思考與判斷非常有幫助。于是乎,TimeTunnel 命令就誕生了。
?
- 查詢?nèi)蝔indUserById 方法的調(diào)用詳情
**tt -t -n 3 com.example.demo.arthas.user.UserController findUserById **
- 查看所有的tt記錄
?tt -l
?
- 查看tt列表的詳情,比如入?yún)⒑统鰠?/li>
tt -i 1001 (1001是INDEX的值)
?
monitor
對某個方法的調(diào)用進行定時監(jiān)控。
monitor cn.javastack.springbootbestpractice.web.JsonTest getUserInfo -c 5
-c 5:表示每5秒統(tǒng)計一次,統(tǒng)計周期,默認值為120秒。
監(jiān)控維度說明:
監(jiān)控項 | 說明 |
---|---|
timestamp | 時間戳 |
class | 類名 |
method | 方法名 |
total | 調(diào)用次數(shù) |
success | 成功次數(shù) |
fail | 失敗次數(shù) |
rt | 平均響應(yīng)時間 |
fail-rate | 失敗率 |
watch
watch
?命令可以查看函數(shù)的:
- 參數(shù)
- 返回值
- 異常信息
watch demo.MathGame primeFactors returnObj
watch com.example.demo.arthas.user.UserController * '{params, throwExp}' -x 2
- 第一個參數(shù)是類名,支持通配
- 第二個參數(shù)是函數(shù)名,支持通配
- 第三個參數(shù)是定義返回值
-
-x 2
?是為了將結(jié)果展開
返回值表達式實際是一個?ognl
?表示,支持一些內(nèi)置對象:
- loader
- clazz
- method
- target
- params
- returnObj
- throwExp
- isBefore
- isThrow
- isReturn
watch命令支持按請求耗時進行過濾:
watch com.example.demo.arthas.user.UserController * '{params, returnObj}' '#cost>200'
Arthas在 watch/trace 等命令時,實際上是修改了應(yīng)用的字節(jié)碼,插入增強的代碼。顯式執(zhí)行 reset 命令,可以清除掉這些增強代碼
再如
watch cn.javastack.springbootbestpractice.web.JsonTest getUserInfo ‘{params, returnObj}’ -x 2 -b
?
以上監(jiān)控的是一個方法的入?yún)⑶闆r,在方法執(zhí)行前監(jiān)控:-b,遍歷深度:-x 2。
vmoption查看/更新虛擬機參數(shù)
這個命令可以看到我們的java項目在運行時設(shè)置了哪些參數(shù),命令沒有參數(shù)時會打印所有的vm參數(shù)
- vmoption?查詢?nèi)刻摂M數(shù)據(jù)
- vmoption PrintGCDetails?查看指定的vm參數(shù)
- vmoption PrintGCDetails true?更新指定的vm參數(shù)
sysprop
命令可以查看當(dāng)前JVM的系統(tǒng)屬性(System Property)
?
- sysprop?查詢?nèi)縅VM系統(tǒng)屬性
- sysprop java.version?查詢單個屬性
- sysprop user.country CN?修改單個屬性
getstatic
通過getstatic命令可以方便的查看類的靜態(tài)屬性
?
- getstatic demo.MathGame random?格式為getstatic <類路徑><靜態(tài)屬性名字>
mc
Memory Compiler/內(nèi)存編譯器,編譯.java文件生成.class。
retransform
加載外部的.class文件,retransform jvm已加載的類
quit/exit
退出當(dāng)前 Arthas。
這個命令僅退出當(dāng)前連接的客戶端,附到目標(biāo)進程上的 Arthas 會繼續(xù)運行,端口不會關(guān)閉,下次連接時可以直接連接使用。
shutdown
關(guān)閉 Arthas 服務(wù)端,退出所有 Arthas 客戶端。
以上演示了 幾個常用命令的基本使用,各種命令的使用詳情可以在命令帶?--help
?進行查閱。
更多其他命令請參考:
https://alibaba.github.io/arthas/commands.html
四、實戰(zhàn)案例
熱更新代碼(修改正在運行的java程序)
注:修改jvm內(nèi)存的代碼需要注意兩個點,第一不能添加新的字段信息,第二添加的代碼如果方法還在執(zhí)行就不會生效
1、下載測試用例
# 下載測試用例
wget https://github.com/hengyunabc/spring-boot-inside/raw/master/demo-arthas-spring-boot/demo-arthas-spring-boot.jar
# 運行用例
nohup java -jar demo-arthas-spring-boot.jar
2、把需要修改的類反編譯出來
# 使用jad 將jvm的class文件反編譯成.java文件保存到臨時目錄
jad --source-only com.example.demo.arthas.user.UserController > /fyx/tmp/UserController.java
3、退出arthas修改代碼
# 退出arthas
stop
# 進入目錄
cd /fyx/tmp
# 修改代碼
vim UserController
package com.example.demo.arthas.user;
import com.example.demo.arthas.user.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("id: {}", (Object)id);
if (id != null && id < 1) {
logger.info("返回的數(shù)值有問題{}",id);
return new User(id.intValue(),"name"+id);
}
return new User(id.intValue(), "name" + id);
}
}
4、查看UserController的類加載器的hashcode
#進入arthas查看UserController的hash碼
[arthas@10181]$ sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash
classLoaderHash 5674cd4d
5、將修改的代碼編譯成字節(jié)碼并添加類加載器
# -c <類加載器hashcode> <需要編譯的路徑> -d <字節(jié)碼生成的路徑>
mc -c 5674cd4d /fyx/tmp/UserController.java -d /fyx/tmp/
?6、加載更改的代碼
retransform /fyx/tmp/com/example/demo/arthas/user/UserController.class
7、訪問修改的代碼
發(fā)現(xiàn)我們新添加的代碼是有效的
?
如果arthas使用完后一定要使用stop命令推出程序,不然下次監(jiān)聽其他服務(wù)的時候就會監(jiān)聽不到。解決方案就是重新進上一次的監(jiān)聽的程序再執(zhí)行一次stop命令。文章來源:http://www.zghlxwxcb.cn/news/detail-724065.html
總結(jié)下來,使用 Arthas 可以很方便的診斷一個 Java 應(yīng)用程序,如:系統(tǒng)數(shù)據(jù)面板、JVM實時運行狀態(tài)、類加載情況、監(jiān)控方法執(zhí)行情況、顯示方法執(zhí)行路徑等。文章來源地址http://www.zghlxwxcb.cn/news/detail-724065.html
到了這里,關(guān)于Java 診斷利器 Arthas使用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!