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程序:
[[email protected] 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:
[email protected]e
[email protected]
[email protected]
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] [email protected][
[email protected][200],
[email protected][操作成功],
[email protected][Page 1 of 48 containing com.gbcom.gidata.alarm.entity.primary.ActiveAlarm instances],
]
ts=2019-07-29 14:46:08; [cost=1848.329463ms] [email protected][
[email protected][200],
[email protected][操作成功],
[email protected][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] [email protected][][
@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] [email protected][][
@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] [email protected][10]
ts=2019-07-29 14:48:40; [cost=77.127107ms] [email protected][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/ P I D / {PID}/ PID/{JobId})中;
此時任務的執行不受session斷開的影響;任務預設逾時時間是1天,可以通過全局 options 指令修改預設逾時時間;
此指令的結果将異步輸出到檔案中;此時不管 save-result 是否為true,都不會再往~/logs/arthas-cache/result.log 中異步寫結果