Arthas是什么?
Arthas
是Alibaba开源的Java诊断工具,深受开发者喜爱。
当你遇到以下类似问题而束手无策时,Arthas
可以帮助你解决:
Arthas
支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 Tab
自动补全功能,进一步方便进行问题的定位和诊断。
其运行的原理如下图所示:
Arthas是我比较喜欢的一个Java诊断工具,下面一起来体验一下吧。
可以通过如下链接下载arthas-boot.jar
https://alibaba.github.io/arthas/arthas-boot.jar
然后用java
-jar
的方式启动:
java -jar arthas-boot.jar
可以通过java -jar arthas-boot.jar -h查看帮助信息:
E:\develop>java -jar arthas-boot.jar -h
[INFO] arthas-boot version: 3.1.7
Usage: arthas-boot [--telnet-port <value>] [--target-ip <value>] [--repo-mirror
<value>] [--http-port <value>] [--versions] [--arthas-home <value>]
[--attach-only] [--use-http] [-h] [--session-timeout <value>]
[--use-version <value>] [-v] [-f <value>] [--tunnel-server <value>]
[--width <value>] [--agent-id <value>] [--stat-url <value>] [-c <value>]
[--height <value>] [pid]
Bootstrap Arthas
EXAMPLES:
java -jar arthas-boot.jar <pid>
java -jar arthas-boot.jar --target-ip 0.0.0.0
java -jar arthas-boot.jar --telnet-port 9999 --http-port -1
java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
--agent-id bvDOe8XbTM2pQWjF4cfw
java -jar arthas-boot.jar --stat-url 'http://192.168.10.11:8080/api/stat'
java -jar arthas-boot.jar -c 'sysprop; thread' <pid>
java -jar arthas-boot.jar -f batch.as <pid>
java -jar arthas-boot.jar --use-version 3.1.7
java -jar arthas-boot.jar --versions
java -jar arthas-boot.jar --session-timeout 3600
java -jar arthas-boot.jar --attach-only
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
WIKI:
https://alibaba.github.io/arthas
Options and Arguments:
--telnet-port <value> The target jvm listen telnet port, default 3658
--target-ip <value> The target jvm listen ip, default 127.0.0.1
--repo-mirror <value> Use special maven repository mirror, value is
center/aliyun or http repo url.
--http-port <value> The target jvm listen http port, default 8563
--versions List local and remote arthas versions
--arthas-home <value> The arthas home
--attach-only Attach target process only, do not connect
--use-http Enforce use http to download, default use https
-h,--help Print usage
--session-timeout <value> The session timeout seconds, default 1800
(30min)
--use-version <value> Use special version arthas
-v,--verbose Verbose, print debug info.
-f,--batch-file <value> The batch file to execute
--tunnel-server <value> The tunnel server url
--width <value> arthas-client terminal width
--agent-id <value> The agent id register to tunnel server
--stat-url <value> The report stat url
-c,--command <value> Command to execute, multiple commands separated
by ;
--height <value> arthas-client terminal height
<pid> Target pid
E:\develop>
接下来,先体验一下Arthas的最基本的操作。
启动一个24点小程序:
import java.util.*;
public class Game24Player {
final String[] patterns = {"nnonnoo", "nnonono", "nnnoono", "nnnonoo",
"nnnnooo"};
final String ops = "+-*/^";
String solution;
List<Integer> digits;
public static void main(String[] args) {
new Game24Player().play();
}
void play() {
digits = getSolvableDigits();
Scanner in = new Scanner(System.in);
while (true) {
//System.out.print("Make 24 using these digits: ");
System.out.print("使用如下数字计算24点: ");
System.out.println(digits);
System.out.println("(输入 'q' 退出, 输入's'获取一种解决方法 )");
System.out.print("> ");
String line = in.nextLine();
if (line.equalsIgnoreCase("q")) {
System.out.println("\n谢谢");
return;
}
if (line.equalsIgnoreCase("s")) {
System.out.println(solution);
digits = getSolvableDigits();
continue;
}
char[] entry = line.replaceAll("[^*+-/)(\\d]", "").toCharArray();
try {
validate(entry);
if (evaluate(infixToPostfix(entry))) {
System.out.println("\n正确! 再来一次? ");
digits = getSolvableDigits();
} else {
System.out.println("\n不正确.");
}
} catch (Exception e) {
System.out.printf("%n%s Try again.%n", e.getMessage());
}
}
}
void validate(char[] input) throws Exception {
int total1 = 0, parens = 0, opsCount = 0;
for (char c : input) {
if (Character.isDigit(c))
total1 += 1 << (c - '0') * 4;
else if (c == '(')
parens++;
else if (c == ')')
parens--;
else if (ops.indexOf(c) != -1)
opsCount++;
if (parens < 0)
throw new Exception("Parentheses mismatch.");
}
if (parens != 0)
throw new Exception("Parentheses mismatch.");
if (opsCount != 3)
throw new Exception("Wrong number of operators.");
int total2 = 0;
for (int d : digits)
total2 += 1 << d * 4;
if (total1 != total2)
throw new Exception("Not the same digits.");
}
boolean evaluate(char[] line) throws Exception {
Stack<Float> s = new Stack<>();
try {
for (char c : line) {
if ('0' <= c && c <= '9')
s.push((float) c - '0');
else
s.push(applyOperator(s.pop(), s.pop(), c));
}
} catch (EmptyStackException e) {
throw new Exception("Invalid entry.");
}
return (Math.abs(24 - s.peek()) < 0.001F);
}
float applyOperator(float a, float b, char c) {
switch (c) {
case '+':
return a + b;
case '-':
return b - a;
case '*':
return a * b;
case '/':
return b / a;
default:
return Float.NaN;
}
}
List<Integer> randomDigits() {
Random r = new Random();
List<Integer> result = new ArrayList<>(4);
for (int i = 0; i < 4; i++)
result.add(r.nextInt(9) + 1);
return result;
}
List<Integer> getSolvableDigits() {
List<Integer> result;
do {
result = randomDigits();
} while (!isSolvable(result));
return result;
}
boolean isSolvable(List<Integer> digits) {
Set<List<Integer>> dPerms = new HashSet<>(4 * 3 * 2);
permute(digits, dPerms, 0);
int total = 4 * 4 * 4;
List<List<Integer>> oPerms = new ArrayList<>(total);
permuteOperators(oPerms, 4, total);
StringBuilder sb = new StringBuilder(4 + 3);
for (String pattern : patterns) {
char[] patternChars = pattern.toCharArray();
for (List<Integer> dig : dPerms) {
for (List<Integer> opr : oPerms) {
int i = 0, j = 0;
for (char c : patternChars) {
if (c == 'n')
sb.append(dig.get(i++));
else
sb.append(ops.charAt(opr.get(j++)));
}
String candidate = sb.toString();
try {
if (evaluate(candidate.toCharArray())) {
solution = postfixToInfix(candidate);
return true;
}
} catch (Exception ignored) {
}
sb.setLength(0);
}
}
}
return false;
}
String postfixToInfix(String postfix) {
class Expression {
String op, ex;
int prec = 3;
Expression(String e) {
ex = e;
}
Expression(String e1, String e2, String o) {
ex = String.format("%s %s %s", e1, o, e2);
op = o;
prec = ops.indexOf(o) / 2;
}
}
Stack<Expression> expr = new Stack<>();
for (char c : postfix.toCharArray()) {
int idx = ops.indexOf(c);
if (idx != -1) {
Expression r = expr.pop();
Expression l = expr.pop();
int opPrec = idx / 2;
if (l.prec < opPrec)
l.ex = '(' + l.ex + ')';
if (r.prec <= opPrec)
r.ex = '(' + r.ex + ')';
expr.push(new Expression(l.ex, r.ex, "" + c));
} else {
expr.push(new Expression("" + c));
}
}
return expr.peek().ex;
}
char[] infixToPostfix(char[] infix) throws Exception {
StringBuilder sb = new StringBuilder();
Stack<Integer> s = new Stack<>();
try {
for (char c : infix) {
int idx = ops.indexOf(c);
if (idx != -1) {
if (s.isEmpty())
s.push(idx);
else {
while (!s.isEmpty()) {
int prec2 = s.peek() / 2;
int prec1 = idx / 2;
if (prec2 >= prec1)
sb.append(ops.charAt(s.pop()));
else
break;
}
s.push(idx);
}
} else if (c == '(') {
s.push(-2);
} else if (c == ')') {
while (s.peek() != -2)
sb.append(ops.charAt(s.pop()));
s.pop();
} else {
sb.append(c);
}
}
while (!s.isEmpty())
sb.append(ops.charAt(s.pop()));
} catch (EmptyStackException e) {
throw new Exception("Invalid entry.");
}
return sb.toString().toCharArray();
}
void permute(List<Integer> lst, Set<List<Integer>> res, int k) {
for (int i = k; i < lst.size(); i++) {
Collections.swap(lst, i, k);
permute(lst, res, k + 1);
Collections.swap(lst, k, i);
}
if (k == lst.size())
res.add(new ArrayList<>(lst));
}
void permuteOperators(List<List<Integer>> res, int n, int total) {
for (int i = 0, npow = n * n; i < total; i++)
res.add(Arrays.asList((i / npow), (i % npow) / n, i % n));
}
}
运行一下,可以输入‘s’输出算24的一个解:
使用如下数字计算24点: [9, 7, 4, 3]
(输入 'q' 退出, 输入's'获取一种解决方法 )
> s
3 * 9 + (4 - 7)
使用如下数字计算24点: [1, 7, 6, 3]
(输入 'q' 退出, 输入's'获取一种解决方法 )
>
启动Arthas
E:\develop>java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.1.7
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 24756
[2]: 12556 Game24Player
然后选择Game24Player作为我们要观察java进行,输入2,然后按回车键,可以看到如下信息:
Dashboard
输入dashboard,按回车/enter,会展示当前进程的信息,按ctrl+c可以中断执行。如:
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
5 Attach Listener system 5 RUNNABLE 0 0:0 false true
11 Common-Cleaner InnocuousThreadGroup 8 TIMED_WAITING 0 0:0 false true
3 Finalizer system 8 WAITING 0 0:0 false true
2 Reference Handler system 10 RUNNABLE 0 0:0 false true
4 Signal Dispatcher system 9 RUNNABLE 0 0:0 false true
21 Timer-for-arthas-dashboard-07e86b84-00bb-4e5d-86fe-e32ba43 system 10 RUNNABLE 0 0:0 false true
16 arthas-shell-server system 5 TIMED_WAITING 0 0:0 false true
20 as-command-execute-daemon system 10 TIMED_WAITING 0 0:0 false true
13 job-timeout system 5 TIMED_WAITING 0 0:0 false true
1 main main 5 RUNNABLE 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNABLE 0 0:0 false false
19 nioEventLoopGroup-2-2 system 10 RUNNABLE 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNABLE 0 0:0 false false
17 pool-1-thread-1 system 5 WAITING 0 0:0 false false
thread
1
会打印线程ID 1的栈,通常是main函数的线程。
输入thread 1 | grep 'main('
[arthas@12556]$ thread 1 | grep 'main('
at app//Game24Player.main(Game24Player.java:14)
通过jad来反编译Main Class
[arthas@12556]$ jad Game24Player
ClassLoader:
+-jdk.internal.loader.ClassLoaders$AppClassLoader@15db9742
+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@1a4aa43e
Location:
/E:/develop/workspace/Test/bin/
/*
* Decompiled with CFR.
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EmptyStackException;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
import java.util.Stack;
public class Game24Player {
final String[] patterns = new String[]{"nnonnoo", "nnonono", "nnnoono", "nnnonoo", "nnnnooo"};
final String ops;
String solution;
List<Integer> digits;
public Game24Player() {
this.ops = "+-*/^";
}
public static void main(String[] args) {
new Game24Player().play();
}
... ...
... ...
基础命令
help——查看命令帮助信息
cat——打印文件内容,和linux里的cat命令类似
echo–打印参数,和linux里的echo命令类似
grep——匹配查找,和linux里的grep命令类似
tee——复制标准输入到标准输出和指定的文件,和linux里的tee命令类似
pwd——返回当前的工作目录,和linux命令类似
cls——清空当前屏幕区域
session——查看当前会话的信息
reset——重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
version——输出当前目标 Java 进程所加载的 Arthas 版本号
history——打印命令历史
quit——退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
stop——关闭 Arthas 服务端,所有 Arthas 客户端全部退出
keymap——Arthas快捷键列表及自定义快捷键
[arthas@12556]$ session
Name Value
--------------------------------------------------
JAVA_PID 12556
SESSION_ID c5eab2c5-4131-4b2a-849d-0d02124d185f
[arthas@12556]$
[arthas@12556]$
[arthas@12556]$ keymap
Shortcut Description Name
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
"\C-a" Ctrl + a beginning-of-line
"\C-e" Ctrl + e end-of-line
"\C-f" Ctrl + f forward-word
"\C-b" Ctrl + b backward-word
"\e[D" Left arrow backward-char
"\e[C" Right arrow forward-char
"\e[A" Up arrow history-search-backward
"\e[B" Down arrow history-search-forward
"\C-h" Ctrl + h backward-delete-char
"\C-?" Ctrl + ? backward-delete-char
"\C-u" Ctrl + u undo
"\C-d" Ctrl + d delete-char
"\C-k" Ctrl + k kill-line
"\C-i" Ctrl + i complete
"\C-j" Ctrl + j accept-line
"\C-m" Ctrl + m accept-line
"\C-w" Ctrl + w backward-delete-word
"\C-x\e[3~" "\C-x\e[3~" backward-kill-line
"\e\C-?" "\e\C-?" backward-kill-word
"\e[1~" "\e[1~" beginning-of-line
"\e[4~" "\e[4~" end-of-line
"\e[5~" "\e[5~" beginning-of-history
"\e[6~" "\e[6~" end-of-history
"\e[3~" "\e[3~" delete-char
"\e[2~" "\e[2~" quoted-insert
"\e[7~" "\e[7~" beginning-of-line
"\e[8~" "\e[8~" end-of-line
"\eOH" "\eOH" beginning-of-line
"\eOF" "\eOF" end-of-line
"\e[H" "\e[H" beginning-of-line
"\e[F" "\e[F" end-of-line
[arthas@12556]$ pwd
E:\develop\workspace\Test
[arthas@12556]$
[arthas@12556]$
[arthas@12556]$ version
3.2.0
[arthas@12556]$
jvm相关
dashboard——当前系统的实时数据面板
thread——查看当前 JVM 的线程堆栈信息
jvm——查看当前 JVM 的信息
sysprop——查看和修改JVM的系统属性
sysenv——查看JVM的环境变量
vmoption——查看和修改JVM里诊断相关的option
perfcounter——查看当前 JVM 的Perf Counter信息
logger——查看和修改logger
getstatic——查看类的静态属性
ognl——执行ognl表达式
mbean——查看 Mbean 的信息
heapdump——dump java heap, 类似jmap命令的heap dump功能
如:
产生堆快照
[arthas@12556]$ heapdump
Dumping heap to C:\Users\WANGME~1\AppData\Local\Temp\heapdump2020-05-06-23-099555333078403039860.hprof...
Heap dump file created
[arthas@12556]$
class/classloader相关
sc——查看JVM已加载的类信息
sm——查看已加载类的方法信息
jad——反编译指定已加载类的源码
mc——内存编绎器,内存编绎.java文件为.class文件
redefine——加载外部的.class文件,redefine到JVM里
dump——dump 已加载类的 byte code 到特定目录
classloader——查看classloader的继承树,urls,类加载信息,使用classloader去getResource
如:
[arthas@12556]$ classloader
name numberOfInstances loadedCountTotal
BootstrapClassLoader 1 2389
com.taobao.arthas.agent.ArthasClassloader 1 2304
jdk.internal.reflect.DelegatingClassLoader 7 7
jdk.internal.loader.ClassLoaders$AppClassLoader 1 5
jdk.internal.loader.ClassLoaders$PlatformClassLoader 1 4
Affect(row-cnt:5) cost in 3 ms.
[arthas@12556]$
monitor/watch/trace相关
请注意
请注意,这些命令,都通过字节码增强技术来实现的, 会在指定类的方法中插入一些切面来实现数据统计和观测, 因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件, 诊断结束要执行 stop 或将增强过的类执行 reset 命令。
monitor——方法执行监控
watch——方法执行数据观测
trace——方法内部调用路径,并输出方法路径上的每个节点上耗时
stack——输出当前方法被调用的调用路径
tt——方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
如Monitor:多运行一下24点的程序,这样的话程序会去调用随机数方法,从而能被监控到:
[arthas@12556]$ monitor -c 5 Game24Player randomDigits
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 17 ms.
timestamp class method total success fail avg-rt(ms) fail-rate
----------------------------------------------------------------------------------------------
2020-05-06 23:15:14 Game24Player randomDigits 1 1 0 0.65 0.00%
timestamp class method total success fail avg-rt(ms) fail-rate
----------------------------------------------------------------------------------------------
2020-05-06 23:15:19 Game24Player randomDigits 7 7 0 0.16 0.00%
timestamp class method total success fail avg-rt(ms) fail-rate
----------------------------------------------------------------------------------------------
2020-05-06 23:15:24 Game24Player randomDigits 0 0 0 0.00 0.00%
timestamp class method total success fail avg-rt(ms) fail-rate
----------------------------------------------------------------------------------------------
2020-05-06 23:15:29 Game24Player randomDigits 0 0 0 0.00 0.00%
又如stack
输出当前方法被调用的调用路径
很多时候我们都知道一个方法被执行,
但这个方法被执行的路径非常多,
或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
Arthas还支持很多的命令,可以通过如下链接阅读:
https://alibaba.github.io/arthas/advanced-use.html
经过上述的一些介绍,相信读者对Arthas有了一定的了解。
平时我们在开发的时候,可以通过monitor命令对方法的耗时进行监控;也可以通过thread命令的查找快速找到死锁的程序,再来看一个死锁的场景吧,一个简单的死锁程序:
public class ThreadDeadLock implements Runnable{
int a;
int b;
public ThreadDeadLock(int a, int b){
this.a = a;
this.b = b;
}
@Override
public void run() {
synchronized (Integer.valueOf(a)) {
synchronized(Integer.valueOf(b)){
System.out.println(a + b);
}
}
}
public static void main(String[] args) {
for(int i=0; i<100; ++i){
new Thread(new ThreadDeadLock(1, 2)).start();
new Thread(new ThreadDeadLock(2, 1)).start();
}
}
}
运行一下,然后使用java -jar arthas-boot.jar启动Arthas,选择死锁程序的java进程,选择1,然后按回车;
E:\develop>java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.1.7
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 20836 ThreadDeadLock
[2]: 24756
1
[INFO] arthas home: C:\Users\wangmengjun\.arthas\lib\3.2.0\arthas
[INFO] Try to attach process 20836
[INFO] Attach process 20836 success.
[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.2.0
pid 20836
time 2020-05-06 23:31:43
然后,使用thread命令:
我们可以看到很多BLOCKED的线程,这些其实就是我们运行死锁程序导致的。然后针对某一个,比如id为180的线程进行分析:
[arthas@20836]$ thread 180
"Thread-168" Id=180 BLOCKED on java.lang.Integer@40e3a44f owned by "Thread-154" Id=166
at app//ThreadDeadLock.run(ThreadDeadLock.java:13)
- blocked on java.lang.Integer@40e3a44f
at java.base@12.0.2/java.lang.Thread.run(Thread.java:835)
Affect(row-cnt:0) cost in 37 ms.
[arthas@20836]$
我们很容易就知道BLOCKED是程序ThreadDeadLock导致的,很方便。
夜深了,就写到这里吧。有兴趣的同学可以到Arthas官网进一步了解。
参考
[1]. Arthas用户文档. https://alibaba.github.io/arthas/index.html
[2]. Arthas使用介绍.
https://www.cnblogs.com/qiaoyihang/p/10533672.html