1.Arthas 能爲你做什麼?
Arthas是Alibaba開源的Java診斷工具,深受開發者喜愛。
當你遇到以下類似問題而束手無策時,Arthas可以幫助你解決:
這個類從哪個 jar 包加載的?爲什麼會報各種類相關的 Exception?
我改的代碼爲什麼沒有執行到?難道是我沒 commit?分支搞錯了?
遇到問題無法在線上 debug,難道只能通過加日誌再重新發布嗎?
線上遇到某個用戶的數據處理有問題,但線上同樣無法 debug,線下無法重現!
是否有一個全局視角來查看系統的運行狀況?
有什麼辦法可以監控到JVM的實時運行狀態?
Arthas支持JDK 6+,支持Linux/Mac/Winodws,採用命令行交互模式,同時提供豐富的 Tab 自動補全功能,進一步方便進行問題的定位和診斷。
GitHub地址:https://github.com/alibaba/arthas
用戶文檔:https://alibaba.github.io/arthas/
2.Arthas Install
2.1使用arthas-boot
下載arthas-boot.jar,然後用java -jar的方式啓動:
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
打印幫助信息
java -jar arthas-boot.jar -h
2.2使用as.sh安裝
Arthas 支持在 Linux/Unix/Mac 等平臺上一鍵安裝,請複製以下內容,並粘貼到命令行中,敲 回車 執行即可:
curl -L https://alibaba.github.io/arthas/install.sh | sh
上述命令會下載啓動腳本文件 as.sh 到當前目錄,你可以放在任何地方或將其加入到 $PATH 中。
直接在shell下面執行./as.sh,就會進入交互界面。
也可以執行./as.sh -h來獲取更多參數信息。
2.3通過Cloud Toolkit插件使用Arthas
IDEA與eclipse插件安裝圖文教程
Cloud Toolkit插件官方介紹
2.4卸載
在 Linux/Unix/Mac 平臺
刪除下面文件:
rm -rf ~/.arthas/
rm -rf ~/logs/arthas
Windows平臺直接刪除user home下面的.arthas和logs/arthas目錄
3.命令列表
3.1基礎命令
help——查看命令幫助信息
cls——清空當前屏幕區域
session——查看當前會話的信息
reset——重置增強類,將被 Arthas 增強過的類全部還原,Arthas 服務端關閉時會重置所有增強過的類
version——輸出當前目標 Java 進程所加載的 Arthas 版本號
history——打印命令歷史
quit——退出當前 Arthas 客戶端,其他 Arthas 客戶端不受影響
shutdown——關閉 Arthas 服務端,所有 Arthas 客戶端全部退出
keymap——Arthas快捷鍵列表及自定義快捷鍵
3.2Jvm相關
dashboard——當前系統的實時數據面板
thread——查看當前 JVM 的線程堆棧信息
jvm——查看當前 JVM 的信息
sysprop——查看和修改JVM的系統屬性
sysenv——查看JVM的環境變量
getstatic——查看類的靜態屬性
ognl——執行ognl表達式
mbean——查看 Mbean 的信息
3.3class/classloader相關
sc——查看JVM已加載的類信息
sm——查看已加載類的方法信息
jad——反編譯指定已加載類的源碼
mc——內存編繹器,內存編繹.java文件爲.class文件
redefine——加載外部的.class文件,redefine到JVM裏
dump——dump 已加載類的 byte code 到特定目錄
classloader——查看classloader的繼承樹,urls,類加載信息,使用classloader去getResource
3.4monitor/watch/trace相關
請注意,這些命令,都通過字節碼增強技術來實現的,會在指定類的方法中插入一些切面來實現數據統計和觀測,因此在線上、預發使用時,請儘量明確需要觀測的類、方法以及條件,診斷結束要執行 shutdown 或將增強過的類執行 reset 命令。
monitor——方法執行監控
watch——方法執行數據觀測
trace——方法內部調用路徑,並輸出方法路徑上的每個節點上耗時
stack——輸出當前方法被調用的調用路徑
tt——方法執行數據的時空隧道,記錄下指定方法每次調用的入參和返回信息,並能對這些不同的時間下調用進行觀測
3.5options
options——查看或設置Arthas全局開關
4實例
4.1.啓動Arthas
在命令行下面執行(使用和目標進程一致的用戶啓動,否則可能attach失敗):
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
執行該程序的用戶需要和目標進程具有相同的權限。比如以admin用戶來執行:sudo su admin && java -jar arthas-boot.jar 或 sudo -u admin -EH java -jar arthas-boot.jar。
如果attach不上目標進程,可以查看~/logs/arthas/ 目錄下的日誌。
如果下載速度比較慢,可以使用aliyun的鏡像:java -jar arthas-boot.jar --repo-mirror aliyun --use-http
java -jar arthas-boot.jar -h 打印更多參數信息。
選擇應用java進程:
[root@wh-gbcom gidata]# java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.1.1
[INFO] Process 54355 already using port 3658
[INFO] Process 54355 already using port 8563
[INFO] Found existing java process, please choose one and hit RETURN.
[1]: 54355 /home/xxx.jar
[2]: 417757 /usr/lib/jenkins/jenkins.war
xxx進程是第1個,則輸入1,再輸入回車/enter。Arthas會attach到目標進程上,並輸出日誌:
[INFO] arthas home: /root/.arthas/lib/3.1.1/arthas
[INFO] The target process already listen port 3658, skip attach.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://alibaba.github.io/arthas
tutorials https://alibaba.github.io/arthas/arthas-tutorials
version 3.1.1
pid 54355
time 2019-07-29 11:13:34
4.2查看dashboard
$ dashboard
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
152 Timer-for-arthas-dashboard-838ac88e-342e-4e52-83ba-b07c3cf5 system 10 RUNNABLE 57 0:0 false true
64 SimplePauseDetectorThread_0 main 5 TIMED_WAITING 12 15:30 false true
66 SimplePauseDetectorThread_2 main 5 TIMED_WAITING 11 15:44 false true
65 SimplePauseDetectorThread_1 main 5 TIMED_WAITING 9 15:32 false true
57 http-nio-9015-ClientPoller-0 main 5 RUNNABLE 2 0:10 false true
143 nioEventLoopGroup-2-1 system 10 RUNNABLE 2 0:0 false false
80 DataPublisher main 5 TIMED_WAITING 1 0:13 false true
35 SimplePauseDetectorThread_0 system 9 TIMED_WAITING 1 1:35 false true
33 Abandoned connection cleanup thread main 5 TIMED_WAITING 0 0:6 false true
140 AsyncAppender-Worker-arthas-cache.result.AsyncAppender system 9 WAITING 0 0:0 false true
41 AsyncResolver-bootstrap-0 main 5 TIMED_WAITING 0 0:0 false true
88 AsyncResolver-bootstrap-executor-0 main 5 WAITING 0 0:0 false true
138 Attach Listener system 9 RUNNABLE 0 0:0 false true
31 Catalina-utility-1 main 1 TIMED_WAITING 0 0:17 false false
87 DestroyJavaVM main 5 RUNNABLE 0 0:31 false false
42 DiscoveryClient-0 main 5 TIMED_WAITING 0 0:1 false true
43 DiscoveryClient-1 main 5 WAITING 0 0:1 false true
83 DiscoveryClient-CacheRefreshExecutor-0 main 5 WAITING 0 0:11 false true
84 DiscoveryClient-HeartbeatExecutor-0 main 5 WAITING 0 0:10 false true
44 DiscoveryClient-InstanceInfoReplicator-0 main 5 TIMED_WAITING 0 0:0 false true
36 Druid-ConnectionPool-Create-438123546 main 5 WAITING 0 0:0 false true
38 Druid-ConnectionPool-Create-609389093 main 5 WAITING 0 0:0 false true
37 Druid-ConnectionPool-Destroy-438123546 main 5 TIMED_WAITING 0 0:0 false true
39 Druid-ConnectionPool-Destroy-609389093 main 5 TIMED_WAITING 0 0:0 false true
40 Eureka-JerseyClient-Conn-Cleaner2 main 5 TIMED_WAITING 0 0:0 false true
3 Finalizer system 8 WAITING 0 0:0 false true
Memory used total max usage GC
heap 566M 1057M 7033M 8.06% gc.ps_scavenge.count 889
ps_eden_space 323M 602M 2623M 12.32% gc.ps_scavenge.time(ms) 15532
ps_survivor_space 6M 7M 7M 99.88% gc.ps_marksweep.count 4
ps_old_gen 236M 448M 5275M 4.48% gc.ps_marksweep.time(ms) 637
nonheap 196M 203M -1 96.52%
code_cache 63M 64M 240M 26.37%
metaspace 118M 124M -1 95.62%
compressed_class_space 14M 15M 1024M 1.43%
direct 88K 88K - 100.00%
mapped 0K 0K - NaN%
Runtime
os.name Linux
os.version 3.10.0-957.21.3.el7.x86_64
java.version 1.8.0_212
java.home /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.212.b04-0.el7_6.x86_64/jre
systemload.average 0.30
processors 32
uptime 246111s
4.3. 使用命令反編譯class文件
$ jad com.gbcom.gidata.alarm.entity.primary.ActiveAlarm
ClassLoader:
+-org.springframework.boot.loader.LaunchedURLClassLoader@49c2faae
+-sun.misc.Launcher$AppClassLoader@70dea4e
+-sun.misc.Launcher$ExtClassLoader@620aa4ea
Location:
file:/home/gidata/GiDataServer/GiDataServer.jar!/BOOT-INF/classes!/
/*
* Decompiled with CFR 0_132.
*
* Could not load the following classes:
* javax.persistence.Entity
* javax.persistence.GeneratedValue
* javax.persistence.GenerationType
* javax.persistence.Id
* javax.persistence.Table
*/
package com.gbcom.gidata.alarm.entity.primary;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="alarm_active")
public class ActiveAlarm {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private String alarmName;
private String sno;
private String name;
private Byte gender;
private String departmentName;
private String professionName;
private String className;
private Date reportTime;
private String idNo;
private String phone;
private String phoneMac;
private String dorm;
private int admissionYear;
private String politic;
private int status;
private String reportDate;
private String operationLog;
public void setClassName(String className) {
this.className = className;
}
public void setId(int id) {
this.id = id;
}
public String getIdNo() {
return this.idNo;
}
public String getPhone() {
return this.phone;
}
public String getPhoneMac() {
return this.phoneMac;
}
public void setIdNo(String idNo) {
this.idNo = idNo;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setPhoneMac(String phoneMac) {
this.phoneMac = phoneMac;
}
public void setProfessionName(String professionName) {
this.professionName = professionName;
}
public String getProfessionName() {
return this.professionName;
}
public String getDepartmentName() {
return this.departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
public void setGender(Byte gender) {
this.gender = gender;
}
public String getOperationLog() {
return this.operationLog;
}
public void setStatus(int status) {
this.status = status;
}
public void setOperationLog(String operationLog) {
this.operationLog = operationLog;
}
public void setReportDate(String reportDate) {
this.reportDate = reportDate;
}
public String getAlarmName() {
return this.alarmName;
}
public String getSno() {
return this.sno;
}
public Date getReportTime() {
return this.reportTime;
}
public int getStatus() {
return this.status;
}
public String getReportDate() {
return this.reportDate;
}
public void setReportTime(Date reportTime) {
this.reportTime = reportTime;
}
public void setSno(String sno) {
this.sno = sno;
}
public Byte getGender() {
return this.gender;
}
public String getDorm() {
return this.dorm;
}
public void setDorm(String dorm) {
this.dorm = dorm;
}
public void setAdmissionYear(int admissionYear) {
this.admissionYear = admissionYear;
}
public String getPolitic() {
return this.politic;
}
public void setPolitic(String politic) {
this.politic = politic;
}
public void setAlarmName(String alarmName) {
this.alarmName = alarmName;
}
public int getAdmissionYear() {
return this.admissionYear;
}
public String toString() {
return "ActiveAlarm{id=" + this.id + ", alarmName='" + this.alarmName + '\'' + ", sno='" + this.sno + '\'' + ", name='" + this.name + '\'' + ", gender=" + this.gender + ", departmentName='" + this.departmentName + '\'' + ", professionName='" + this.professionName + '\'' + ", className='" + this.className + '\'' + ", reportTime=" + this.reportTime + ", idNo='" + this.idNo + '\'' + ", phone='" + this.phone + '\'' + ", phoneMac='" + this.phoneMac + '\'' + ", dorm='" + this.dorm + '\'' + ", admissionYear=" + this.admissionYear + ", politic='" + this.politic + '\'' + ", status=" + this.status + ", reportDate='" + this.reportDate + '\'' + ", operationLog='" + this.operationLog + '\'' + '}';
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return this.id;
}
public String getClassName() {
return this.className;
}
}
Affect(row-cnt:2) cost in 350 ms.
4.4.Watch
4.4.1監控返回值
$ watch com.gbcom.gidata.alarm.controller.ActiveAlarmController findByPage returnObj
Press Q or Ctrl+C to abort.
Affect(class-cnt:2 , method-cnt:2) cost in 361 ms.
ts=2019-07-29 14:46:08; [cost=1677.776414ms] result=@ResultVO[
code=@Integer[200],
msg=@String[操作成功],
data=@PageImpl[Page 1 of 48 containing com.gbcom.gidata.alarm.entity.primary.ActiveAlarm instances],
]
ts=2019-07-29 14:46:08; [cost=1848.329463ms] result=@ResultVO[
code=@Integer[200],
msg=@String[操作成功],
data=@PageImpl[Page 1 of 48 containing com.gbcom.gidata.alarm.entity.primary.ActiveAlarm instances],
]
4.4.2監控參數
監控所有參數
$ watch com.gbcom.gidata.alarm.controller.ActiveAlarmController findByPage params
Press Q or Ctrl+C to abort.
Affect(class-cnt:2 , method-cnt:2) cost in 231 ms.
ts=2019-07-29 14:47:28; [cost=42.880745ms] result=@Object[][
@AlarmDto[AlarmDto [pageNumber=1, pageSize=10, date=null, alamName=null, num=null, idNo=null, phone=null, phoneMac=null]],
]
ts=2019-07-29 14:47:28; [cost=50.596983ms] result=@Object[][
@AlarmDto[AlarmDto [pageNumber=1, pageSize=10, date=null, alamName=null, num=null, idNo=null, phone=null, phoneMac=null]],
]
監控指定參數的某一個值
$ watch com.gbcom.gidata.alarm.controller.ActiveAlarmController findByPage params[0].pageSize
Press Q or Ctrl+C to abort.
Affect(class-cnt:2 , method-cnt:2) cost in 214 ms.
ts=2019-07-29 14:48:40; [cost=28.2795ms] result=@Integer[10]
ts=2019-07-29 14:48:40; [cost=77.127107ms] result=@Integer[10]
4.4.3監控異常
$ watch com.gbcom.gidata.alarm.controller.ActiveAlarmController findByPage throwExp
Press Q or Ctrl+C to abort.
Affect(class-cnt:2 , method-cnt:2) cost in 167 ms.
ts=2019-07-29 14:51:21; [cost=13.121286ms] result=null
ts=2019-07-29 14:51:21; [cost=18.437713ms] result=null
result=null無異常
4.5退出arthas
如果只是退出當前的連接,可以用quit或者exit命令。Attach到目標進程上的arthas還會繼續運行,端口會保持開放,下次連接時可以直接連接上。
如果想完全退出arthas,可以執行shutdown命令。
5日誌
5.1執行結果存日誌
將命令的結果完整保存在日誌文件中,便於後續進行分析
默認情況下,該功能是關閉的,如果需要開啓,請執行以下命令:
$ options save-result true
NAME BEFORE-VALUE AFTER-VALUE
----------------------------------------
save-result false true
Affect(row-cnt:1) cost in 3 ms.
看到上面的輸出,即表示成功開啓該功能;
日誌文件路徑
結果會異步保存在:{user.home}/logs/arthas-cache/result.log,請定期進行清理,以免佔據磁盤空間
5.2使用新版本Arthas的異步後臺任務將結果存日誌文件
$ trace Test t >> &
job id : 2
cache location : /Users/admin/logs/arthas-cache/28198/2
此時命令會在後臺異步執行,並將結果異步保存在文件(~/logs/arthas-cache/{JobId})中;
此時任務的執行不受session斷開的影響;任務默認超時時間是1天,可以通過全局 options 命令修改默認超時時間;
此命令的結果將異步輸出到文件中;此時不管 save-result 是否爲true,都不會再往~/logs/arthas-cache/result.log 中異步寫結果