Arthas

安装

使用arthas-boot(推荐)

下载arthas-boot.jar,然后用java -jar的方式启动:

  1. curl -O https://arthas.aliyun.com/arthas-boot.jar
  2. java -jar arthas-boot.jar

打印帮助信息:

  1. java -jar arthas-boot.jar -h

如果下载速度比较慢,可以使用aliyun的镜像:

  1. java -jar arthas-boot.jar --repo-mirror aliyun --use-http

启动arthas

在命令行下面执行(使用和目标进程一致的用户启动,否则可能attach失败):

  1. curl -O https://arthas.aliyun.com/arthas-boot.jar
  2. java -jar arthas-boot.jar
  • 执行该程序的用户需要和目标进程具有相同的权限。比如以admin用户来执行:sudo su admin && java -jar arthas-boot.jarsudo -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进程:

  1. $ $ java -jar arthas-boot.jar
  2. * [1]: 35542
  3. [2]: 71560 arthas-demo.jar

Demo进程是第2个,则输入2,再输入回车/enter。Arthas会attach到目标进程上,并输出日志:

  1. [INFO] Try to attach process 71560
  2. [INFO] Attach process 71560 success.
  3. [INFO] arthas-client connect 127.0.0.1 3658
  4. ,---. ,------. ,--------.,--. ,--. ,---. ,---.
  5. / O \ | .--. ''--. .--'| '--' | / O \ ' .-'
  6. | .-. || '--'.' | | | .--. || .-. |`. `-.
  7. | | | || |\ \ | | | | | || | | |.-' |
  8. `--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
  9. wiki: https://arthas.aliyun.com/doc
  10. version: 3.0.5.20181127201536
  11. pid: 71560
  12. time: 2018-11-28 19:16:24

基础命令

  • help——查看命令帮助信息
  • cat——打印文件内容,和linux里的cat命令类似
  • echo–打印参数,和linux里的echo命令类似
  • grep——匹配查找,和linux里的grep
  • base64——base64编码转换,和linux里的base64命令类似
  • tee——复制标准输入到标准输出和指定的文件,和linux里的tee命令类似
  • pwd——返回当前的工作目录,和linux命令类似
  • cls——清空当前屏幕区域
  • session——查看当前会话的信息
  • reset——重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
  • version——输出当前目标 Java 进程所加载的 Arthas 版本号
  • history——打印命令历史
  • quit——退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
  • stop——关闭 Arthas 服务端,所有 Arthas 客户端全部退出
  • keymap——Arthas快捷键列表及自定义快捷键

grep

类似传统的grep命令。

  1. USAGE:
  2. grep [-A <value>] [-B <value>] [-C <value>] [-h] [-i] [-v] [-n] [-m <value>] [-e] [--trim-end] pattern
  3. SUMMARY:
  4. grep command for pipes.
  5. EXAMPLES:
  6. sysprop | grep java
  7. sysprop | grep java -n
  8. sysenv | grep -v JAVA
  9. sysenv | grep -e "(?i)(JAVA|sun)" -m 3 -C 2
  10. sysenv | grep JAVA -A2 -B3
  11. thread | grep -m 10 -e "TIMED_WAITING|WAITING"
  12. WIKI:
  13. https://arthas.aliyun.com/doc/grep
  14. OPTIONS:
  15. -A, --after-context <value> Print NUM lines of trailing context)
  16. -B, --before-context <value> Print NUM lines of leading context)
  17. -C, --context <value> Print NUM lines of output context)
  18. -h, --help this help
  19. -i, --ignore-case Perform case insensitive matching. By default, grep is case sensitive.
  20. -v, --invert-match Select non-matching lines
  21. -n, --line-number Print line number with output lines
  22. -m, --max-count <value> stop after NUM selected lines)
  23. -e, --regex Enable regular expression to match
  24. --trim-end Remove whitespaces at the end of the line
  25. <pattern> Pattern

base64

base64编码转换,和linux里的 base64 命令类似。

对文件进行 base64 编码
  1. base64 /tmp/test.txt

对文件进行 base64 编码并把结果保存到文件里
  1. base64 --input /tmp/test.txt --output /tmp/result.txt

用 base64 解码文件
  1. base64 -d /tmp/result.txt

用 base64 解码文件并保存结果到文件里
  1. base64 -d /tmp/result.txt --output /tmp/bbb.txt

tee

类似传统的tee命令, 用于读取标准输入的数据,并将其内容输出成文件。

tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。

  1. USAGE:
  2. tee [-a] [-h] [file]
  3. SUMMARY:
  4. tee command for pipes.
  5. EXAMPLES:
  6. sysprop | tee /path/to/logfile | grep java
  7. sysprop | tee -a /path/to/logfile | grep java
  8. WIKI:
  9. https://arthas.aliyun.com/doc/tee
  10. OPTIONS:
  11. -a, --append Append to file
  12. -h, --help this help
  13. <file> File path

reset

重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端stop时会重置所有增强过的类

  1. USAGE:
  2. reset [-h] [-E] [class-pattern]
  3. SUMMARY:
  4. Reset all the enhanced classes
  5. EXAMPLES:
  6. reset
  7. reset *List
  8. reset -E .*List
  9. OPTIONS:
  10. -h, --help this help
  11. -E, --regex Enable regular expression to match (wildcard matching by default)
  12. <class-pattern> Path and classname of Pattern Matching

还原指定类
  1. reset Test

还原所有类
  1. reset

快捷键

默认的快捷键如下:
快捷键 快捷键说明 命令名称 命令说明
"\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" 键盘左方向键 backward-char 光标向前移动一个字符
"\e[C" 键盘右方向键 forward-char 光标向后移动一个字符
"\e[B" 键盘下方向键 next-history 下翻显示下一个命令
"\e[A" 键盘上方向键 previous-history 上翻显示上一个命令
"\C-h" ctrl + h backward-delete-char 向后删除一个字符
"\C-?" ctrl + shift + / 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 自动补全,相当于敲TAB
"\C-j" ctrl + j accept-line 结束当前行,相当于敲回车
"\C-m" ctrl + m accept-line 结束当前行,相当于敲回车
"\C-w" backward-delete-word
"\C-x\e[3~" backward-kill-line
"\e\C-?" backward-kill-word
  • 任何时候 tab 键,会根据当前的输入给出提示
  • 命令后敲 --- ,然后按 tab 键,可以展示出此命令具体的选项

自定义快捷键

在当前用户目录下新建$USER_HOME/.arthas/conf/inputrc文件,加入自定义配置。

假设我是vim的重度用户,我要把ctrl+h设置为光标向前一个字符,则设置如下,首先拷贝默认配置

  1. "\C-a": beginning-of-line
  2. "\C-e": end-of-line
  3. "\C-f": forward-word
  4. "\C-b": backward-word
  5. "\e[D": backward-char
  6. "\e[C": forward-char
  7. "\e[B": next-history
  8. "\e[A": previous-history
  9. "\C-h": backward-delete-char
  10. "\C-?": backward-delete-char
  11. "\C-u": undo
  12. "\C-d": delete-char
  13. "\C-k": kill-line
  14. "\C-i": complete
  15. "\C-j": accept-line
  16. "\C-m": accept-line
  17. "\C-w": backward-delete-word
  18. "\C-x\e[3~": backward-kill-line
  19. "\e\C-?": backward-kill-word

然后把"\C-h": backward-delete-char换成"\C-h": backward-char,然后重新连接即可。

后台异步命令相关快捷键
  • ctrl + c: 终止当前命令
  • ctrl + z: 挂起当前命令,后续可以 bg/fg 重新支持此命令,或 kill 掉
  • ctrl + a: 回到行首
  • ctrl + e: 回到行尾

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功能

dashboard

当前系统的实时数据面板,按 ctrl+c 退出。

参数说明
参数名称 参数说明
[i:] 刷新实时数据的时间间隔 (ms),默认5000ms
[n:] 刷新实时数据的次数
  1. ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTE DAEMON
  2. -1 C2 CompilerThread0 - -1 - 1.55 0.077 0:8.684 false true
  3. 53 Timer-for-arthas-dashboard-07b system 5 RUNNABLE 0.08 0.004 0:0.004 false true
  4. 22 scheduling-1 main 5 TIMED_WAI 0.06 0.003 0:0.287 false false
  5. -1 C1 CompilerThread0 - -1 - 0.06 0.003 0:2.171 false true
  6. -1 VM Periodic Task Thread - -1 - 0.03 0.001 0:0.092 false true
  7. 49 arthas-NettyHttpTelnetBootstra system 5 RUNNABLE 0.02 0.001 0:0.156 false true
  8. 16 Catalina-utility-1 main 1 TIMED_WAI 0.0 0.000 0:0.029 false false
  9. -1 G1 Young RemSet Sampling - -1 - 0.0 0.000 0:0.019 false true
  10. 17 Catalina-utility-2 main 1 WAITING 0.0 0.000 0:0.025 false false
  11. 34 http-nio-8080-ClientPoller main 5 RUNNABLE 0.0 0.000 0:0.016 false true
  12. 23 http-nio-8080-BlockPoller main 5 RUNNABLE 0.0 0.000 0:0.011 false true
  13. -1 VM Thread - -1 - 0.0 0.000 0:0.032 false true
  14. -1 Service Thread - -1 - 0.0 0.000 0:0.006 false true
  15. -1 GC Thread#5 - -1 - 0.0 0.000 0:0.043 false true
  16. Memory used total max usage GC
  17. heap 36M 70M 4096M 0.90% gc.g1_young_generation.count 12
  18. g1_eden_space 6M 18M -1 33.33% 86
  19. g1_old_gen 30M 50M 4096M 0.74% gc.g1_old_generation.count 0
  20. g1_survivor_space 491K 2048K -1 24.01% gc.g1_old_generation.time(ms) 0
  21. nonheap 66M 69M -1 96.56%
  22. codeheap_'non-nmethods' 1M 2M 5M 22.39%
  23. metaspace 46M 47M -1 98.01%
  24. Runtime
  25. os.name Mac OS X
  26. os.version 10.15.4
  27. java.version 15
  28. java.home /Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home
  29. systemload.average 10.68
  30. processors 8
  31. uptime 272s

数据说明
  • ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应。
  • NAME: 线程名
  • GROUP: 线程组名
  • PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
  • STATE: 线程的状态
  • CPU%: 线程的cpu使用率。比如采样间隔1000ms,某个线程的增量cpu时间为100ms,则cpu使用率=100/1000=10%
  • DELTA_TIME: 上次采样之后线程运行增量CPU时间,数据格式为
  • TIME: 线程运行总CPU时间,数据格式为分:秒
  • INTERRUPTED: 线程当前的中断位状态
  • DAEMON: 是否是daemon线程

JVM内部线程

Java 8之后支持获取JVM内部线程CPU时间,这些线程只有名称和CPU时间,没有ID及状态等信息(显示ID为-1)。 通过内部线程可以观测到JVM活动,如GC、JIT编译等占用CPU情况,方便了解JVM整体运行状况。

  • 当JVM 堆(heap)/元数据(metaspace)空间不足或OOM时,可以看到GC线程的CPU占用率明显高于其他的线程。
  • 当执行trace/watch/tt/redefine等命令后,可以看到JIT线程活动变得更频繁。因为JVM热更新class字节码时清除了此class相关的JIT编译结果,需要重新编译。

JVM内部线程包括下面几种:

  • JIT编译线程: 如 C1 CompilerThread0, C2 CompilerThread0
  • GC线程: 如GC Thread0, G1 Young RemSet Sampling
  • 其它内部线程: 如VM Periodic Task Thread, VM Thread, Service Thread

thread

查看当前线程信息,查看线程的堆栈

参数说明
参数名称 参数说明
id 线程id
[n:] 指定最忙的前N个线程并打印堆栈
[b] 找出当前阻塞其他线程的线程
[i <value>] 指定cpu使用率统计的采样间隔,单位为毫秒,默认值为200
[—all] 显示所有匹配的线程

使用参考

支持一键展示当前最忙的前N个线程并打印堆栈:

  1. thread -n 3

显示

  1. "C1 CompilerThread0" [Internal] cpuUsage=1.63% deltaTime=3ms time=1170ms
  2. "arthas-command-execute" Id=23 cpuUsage=0.11% deltaTime=0ms time=401ms RUNNABLE
  3. at java.management@11.0.7/sun.management.ThreadImpl.dumpThreads0(Native Method)
  4. at java.management@11.0.7/sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:466)
  5. at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:199)
  6. at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:122)
  7. at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
  8. at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
  9. at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
  10. at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
  11. at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)
  12. at java.base@11.0.7/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
  13. at java.base@11.0.7/java.util.concurrent.FutureTask.run(FutureTask.java:264)
  14. at java.base@11.0.7/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
  15. at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
  16. at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
  17. at java.base@11.0.7/java.lang.Thread.run(Thread.java:834)
  18. "VM Periodic Task Thread" [Internal] cpuUsage=0.07% deltaTime=0ms time=584ms
  • 没有线程ID,包含[Internal]表示为JVM内部线程,参考dashboard命令的介绍。
  • cpuUsage为采样间隔时间内线程的CPU使用率,与dashboard命令的数据一致。
  • deltaTime为采样间隔时间内线程的增量CPU时间,小于1ms时被取整显示为0ms。
  • time 线程运行总CPU时间。

查看指定状态的线程
  1. thread --state WAITING

显示

  1. Threads Total: 16, NEW: 0, RUNNABLE: 9, BLOCKED: 0, WAITING: 3, TIMED_WAITING: 4, TERMINATED: 0
  2. ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTE DAEMON
  3. 3 Finalizer system 8 WAITING 0.0 0.000 0:0.000 false true
  4. 20 arthas-UserStat system 9 WAITING 0.0 0.000 0:0.001 false true
  5. 14 arthas-timer system 9 WAITING 0.0 0.000 0:0.000 false true

jvm

查看当前JVM信息

  1. RUNTIME
  2. --------------------------------------------------------------------------------------------------------------
  3. MACHINE-NAME 37@ff267334bb65
  4. JVM-START-TIME 2020-07-23 07:50:36
  5. MANAGEMENT-SPEC-VERSION 1.2
  6. SPEC-NAME Java Virtual Machine Specification
  7. SPEC-VENDOR Oracle Corporation
  8. SPEC-VERSION 1.8
  9. VM-NAME Java HotSpot(TM) 64-Bit Server VM
  10. VM-VENDOR Oracle Corporation
  11. VM-VERSION 25.201-b09
  12. INPUT-ARGUMENTS []
  13. CLASS-PATH demo-arthas-spring-boot.jar
  14. BOOT-CLASS-PATH /usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/j
  15. re/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/sunrsasign.jar:/usr/lib/jvm/
  16. java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/us
  17. r/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/l
  18. ib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/classes
  19. LIBRARY-PATH /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
  20. --------------------------------------------------------------------------------------------------------------
  21. CLASS-LOADING
  22. --------------------------------------------------------------------------------------------------------------
  23. LOADED-CLASS-COUNT 7529
  24. TOTAL-LOADED-CLASS-COUNT 7529
  25. UNLOADED-CLASS-COUNT 0
  26. IS-VERBOSE false
  27. --------------------------------------------------------------------------------------------------------------
  28. COMPILATION
  29. --------------------------------------------------------------------------------------------------------------
  30. NAME HotSpot 64-Bit Tiered Compilers
  31. TOTAL-COMPILE-TIME 14921(ms)
  32. --------------------------------------------------------------------------------------------------------------
  33. GARBAGE-COLLECTORS
  34. --------------------------------------------------------------------------------------------------------------
  35. PS Scavenge name : PS Scavenge
  36. [count/time (ms)] collectionCount : 7
  37. collectionTime : 68
  38. PS MarkSweep name : PS MarkSweep
  39. [count/time (ms)] collectionCount : 1
  40. collectionTime : 47
  41. --------------------------------------------------------------------------------------------------------------
  42. MEMORY-MANAGERS
  43. --------------------------------------------------------------------------------------------------------------
  44. CodeCacheManager Code Cache
  45. Metaspace Manager Metaspace
  46. Compressed Class Space
  47. Copy Eden Space
  48. Survivor Space
  49. MarkSweepCompact Eden Space
  50. Survivor Space
  51. Tenured Gen
  52. --------------------------------------------------------------------------------------------------------------
  53. MEMORY
  54. --------------------------------------------------------------------------------------------------------------
  55. HEAP-MEMORY-USAGE init : 268435456(256.0 MiB)
  56. [memory in bytes] used : 18039504(17.2 MiB)
  57. committed : 181403648(173.0 MiB)
  58. max : 3817865216(3.6 GiB)
  59. NO-HEAP-MEMORY-USAGE init : 2555904(2.4 MiB)
  60. [memory in bytes] used : 33926216(32.4 MiB)
  61. committed : 35176448(33.5 MiB)
  62. max : -1(-1 B)
  63. --------------------------------------------------------------------------------------------------------------
  64. OPERATING-SYSTEM
  65. --------------------------------------------------------------------------------------------------------------
  66. OS Linux
  67. ARCH amd64
  68. PROCESSORS-COUNT 3
  69. LOAD-AVERAGE 29.53
  70. VERSION 4.15.0-52-generic
  71. --------------------------------------------------------------------------------------------------------------
  72. THREAD
  73. --------------------------------------------------------------------------------------------------------------
  74. COUNT 30
  75. DAEMON-COUNT 24
  76. PEAK-COUNT 31
  77. STARTED-COUNT 36
  78. DEADLOCK-COUNT 0
  79. --------------------------------------------------------------------------------------------------------------
  80. FILE-DESCRIPTOR
  81. --------------------------------------------------------------------------------------------------------------
  82. MAX-FILE-DESCRIPTOR-COUNT 1048576
  83. OPEN-FILE-DESCRIPTOR-COUNT 100
  84. Affect(row-cnt:0) cost in 88 ms.

THREAD相关
  • COUNT: JVM当前活跃的线程数
  • DAEMON-COUNT: JVM当前活跃的守护线程数
  • PEAK-COUNT: 从JVM启动开始曾经活着的最大线程数
  • STARTED-COUNT: 从JVM启动开始总共启动过的线程次数
  • DEADLOCK-COUNT: JVM当前死锁的线程数

文件描述符相关
  • MAX-FILE-DESCRIPTOR-COUNT:JVM进程最大可以打开的文件描述符数
  • OPEN-FILE-DESCRIPTOR-COUNT:JVM当前打开的文件描述符数

sysprop

查看当前JVM的系统属性(System Property)

使用参考
  1. USAGE:
  2. sysprop [-h] [property-name] [property-value]
  3. SUMMARY:
  4. Display, and change all the system properties.
  5. EXAMPLES:
  6. sysprop
  7. sysprop file.encoding
  8. sysprop production.mode true
  9. WIKI:
  10. https://arthas.aliyun.com/doc/sysprop
  11. OPTIONS:
  12. -h, --help this help
  13. <property-name> property name
  14. <property-value> property value
  1. sysprop //查看所有属性
  2. sysprop java.version //查看单个属性
  3. sysprop user.country CN //修改单个属性

sysenv

查看当前JVM的环境属性(System Environment Variables)

使用参考
  1. USAGE:
  2. sysenv [-h] [env-name]
  3. SUMMARY:
  4. Display the system env.
  5. EXAMPLES:
  6. sysenv
  7. sysenv USER
  8. WIKI:
  9. https://arthas.aliyun.com/doc/sysenv
  10. OPTIONS:
  11. -h, --help this help
  12. <env-name> env name

vmoption

查看,更新VM诊断相关的参数(用法同sysprop)

perfcounter

查看当前JVM的 Perf Counter(性能计数器)信息

使用参考
  1. $ perfcounter
  2. java.ci.totalTime 2325637411
  3. java.cls.loadedClasses 3403
  4. java.cls.sharedLoadedClasses 0
  5. java.cls.sharedUnloadedClasses 0
  6. java.cls.unloadedClasses 0
  7. java.property.java.version 11.0.4
  8. java.property.java.vm.info mixed mode
  9. java.property.java.vm.name OpenJDK 64-Bit Server VM
  10. ...

可以用-d参数打印更多信息:

  1. $ perfcounter -d
  2. Name Variability Units Value
  3. ---------------------------------------------------------------------------------
  4. java.ci.totalTime Monotonic Ticks 3242526906
  5. java.cls.loadedClasses Monotonic Events 3404
  6. java.cls.sharedLoadedClasses Monotonic Events 0
  7. java.cls.sharedUnloadedClasses Monotonic Events 0
  8. java.cls.unloadedClasses Monotonic Events 0

logger

查看logger信息,更新logger level

getstatic

通过getstatic命令可以方便的查看类的静态属性

用法

  1. getstatic class_name field_name //类名 属性名
  2. getstatic -c 3d4eac69 class_name field_name //-c 类加载器的hashcode
  3. getstatic --classLoaderClass <类加载器全类名> class_name field_name

注意hashcode是变化的,需要先查看当前的ClassLoader信息,使用sc -d <ClassName>提取对应ClassLoader的hashcode

对于只有唯一实例的ClassLoader可以通过--classLoaderClass指定class name

ognl

执行ognl表达式

参数说明
参数名称 参数说明
express 执行的表达式
[c:] 执行表达式的 ClassLoader 的 hashcode,默认值是SystemClassLoader
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[x] 结果对象的展开层次,默认值1

使用参考

调用静态函数:

  1. ognl '@java.lang.System@out.println("hello")'

获取静态类的静态字段:

  1. ognl '@demo.MathGame@random'

通过hashcode指定ClassLoader:

  1. ognl -c 7f9a81e8 @org.springframework.boot.SpringApplication@logger

注意hashcode是变化的,需要先查看当前的ClassLoader信息,提取对应ClassLoader的hashcode。

对于只有唯一实例的ClassLoader可以通过class name指定,使用起来更加方便:

  1. ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader @org.springframework.boot.SpringApplication@logger

执行多行表达式,赋值给临时变量,返回一个List:

  1. ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
  2. @ArrayList[
  3. @String[/opt/java/8.0.181-zulu/jre],
  4. @String[OpenJDK Runtime Environment],
  5. ]

mbean

查看 Mbean 的信息

参数说明
参数名称 参数说明
name-pattern 名称表达式匹配
attribute-pattern 属性名表达式匹配
[m] 查看元信息
[i:] 刷新属性值的时间间隔 (ms)
[n:] 刷新属性值的次数
[E] 开启正则表达式匹配,默认为通配符匹配。仅对属性名有效

heapdump

dump java heap, 类似jmap命令的heap dump功能。

dump到指定文件

  1. heapdump /tmp/dump.hprof

只dump live对象

  1. heapdump --live /tmp/dump.hprof

dump到临时文件

  1. heapdump

class/classloader相关

  • sc——查看JVM已加载的类信息
  • sm——查看已加载类的方法信息
  • jad——反编译指定已加载类的源码
  • mc——内存编译器,内存编译.java文件为.class文件
  • retransform——加载外部的.class文件,retransform到JVM里
  • redefine——加载外部的.class文件,redefine到JVM里
  • dump——dump 已加载类的 byte code 到特定目录
  • classloader——查看classloader的继承树,urls,类加载信息,使用classloader去getResource

sc

查看JVM已加载的类信息

“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,这个命令支持的参数有 [d][E][f][x:]

参数说明
参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
[d] 输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。 如果一个类被多个ClassLoader所加载,则会出现多次
[E] 开启正则表达式匹配,默认为通配符匹配
[f] 输出当前类的成员变量信息(需要配合参数-d一起使用)
[x:] 指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出
[c:] 指定class的 ClassLoader 的 hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[n:] 具有详细信息的匹配类的最大数量(默认为100)
  • 模糊搜索
    1. sc demo.*
  • 打印类的详细信息
    1. $ sc -d demo.MathGame
  • 打印出类的Field信息
    1. sc -d -f demo.MathGame

sm

查看已加载类的方法信息

“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。

sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。

参数说明
参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
[d] 展示每个方法的详细信息
[E] 开启正则表达式匹配,默认为通配符匹配
[c:] 指定class的 ClassLoader 的 hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[n:] 具有详细信息的匹配类的最大数量(默认为100)

jad

反编译指定已加载类的源码

jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;

  • 在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便
  • 当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解

参数说明
参数名称 参数说明
class-pattern 类名表达式匹配
[c:] 类所属 ClassLoader 的 hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[E] 开启正则表达式匹配,默认为通配符匹配

反编译时只显示源代码

默认情况下,反编译结果里会带有ClassLoader信息,通过--source-only选项,可以只打印源代码。方便和mc/retransform命令结合使用。

  1. jad --source-only demo.MathGame

反编译指定的函数

  1. jad demo.MathGame main

反编译时指定ClassLoader

当有多个 ClassLoader 都加载了这个类时,jad 命令会输出对应 ClassLoader 实例的 hashcode,然后你只需要重新执行 jad 命令,并使用参数 -c <hashcode> 就可以反编译指定 ClassLoader 加载的那个类了;

  1. jad org.apache.log4j.Logger -c 69dcaba4

对于只有唯一实例的ClassLoader还可以通过--classLoaderClass指定class name,使用起来更加方便:

--classLoaderClass 的值是ClassLoader的类名,只有匹配到唯一的ClassLoader实例时才能工作,目的是方便输入通用命令,而-c <hashcode>是动态变化的。

mc

Memory Compiler/内存编译器,编译.java文件生成.class

  1. mc /tmp/Test.java

可以通过-c参数指定classloader:

  1. mc -c 327a647b /tmp/Test.java

也可以通过--classLoaderClass参数指定ClassLoader:

  1. mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp

可以通过-d命令指定输出目录:

  1. mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java

编译生成.class文件之后,可以结合retransform命令实现热更新代码。

注意,mc命令有可能失败。如果编译失败可以在本地编译好.class文件,再上传到服务器。具体参考retransform命令说明。

retransform

加载外部的.class文件,retransform jvm已加载的类。

参考:Instrumentation#retransformClasses

retransform(重新转换) 指定的 .class 文件

  1. retransform /tmp/MathGame.class

加载指定的 .class 文件,然后解析出class name,再retransform jvm中已加载的对应的类。每加载一个 .class 文件,则会记录一个 retransform entry.

如果多次执行 retransform 加载同一个 class 文件,则会有多条 retransform entry.

查看 retransform entry

  1. $ retransform -l
  2. Id ClassName TransformCount LoaderHash LoaderClassName
  3. 1 demo.MathGame 1 null null
  • TransformCount 统计在 ClassFileTransformer#transform 函数里尝试返回 entry对应的 .class文件的次数,但并不表明transform一定成功。

删除指定 retransform entry

需要指定 id:

  1. retransform -d 1

删除所有 retransform entry

  1. retransform --deleteAll

显式触发 retransform

  1. $ retransform --classPattern demo.MathGame
  2. retransform success, size: 1, classes:
  3. demo.MathGame

注意:对于同一个类,当存在多个 retransform entry时,如果显式触发 retransform ,则最后添加的entry生效(id最大的)。

消除 retransform 的影响

如果对某个类执行 retransform 之后,想消除影响,则需要:

  • 删除这个类对应的 retransform entry
  • 重新触发 retransform

如果不清除掉所有的 retransform entry,并重新触发 retransform ,则arthas stop时,retransform过的类仍然生效。

结合 jad/mc 命令使用

  1. jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
  2. mc /tmp/UserController.java -d /tmp
  3. retransform /tmp/com/example/demo/arthas/user/UserController.class
  • jad命令反编译,然后可以用其它编译器,比如vim来修改源码
  • mc命令来内存编译修改过的代码
  • 用retransform命令加载新的字节码

上传 .class 文件到服务器的技巧

使用mc命令来编译jad的反编译的代码有可能失败。可以在本地修改代码,编译好后再上传到服务器上。有的服务器不允许直接上传文件,可以使用base64命令来绕过。

  1. 在本地先转换.class文件为base64,再保存为result.txt
    1. base64 < Test.class > result.txt
  1. 到服务器上,新建并编辑result.txt,复制本地的内容,粘贴再保存

  2. 把服务器上的 result.txt还原为.class

    1. base64 -d < result.txt > Test.class
  1. 用md5命令计算哈希值,校验是否一致

retransform的限制

  • 不允许新增加field/method

  • 正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效

    1. public class MathGame {
    2. public static void main(String[] args) throws InterruptedException {
    3. MathGame game = new MathGame();
    4. while (true) {
    5. game.run();
    6. TimeUnit.SECONDS.sleep(1);
    7. // 这个不生效,因为代码一直跑在 while里
    8. System.out.println("in loop");
    9. }
    10. }
    11. public void run() throws InterruptedException {
    12. // 这个生效,因为run()函数每次都可以完整结束
    13. System.out.println("call run()");
    14. try {
    15. int number = random.nextInt();
    16. List<Integer> primeFactors = primeFactors(number);
    17. print(number, primeFactors);
    18. } catch (Exception e) {
    19. System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
    20. }
    21. }

redefine

推荐使用 retransform命令

加载外部的.class文件,redefine jvm已加载的类。

参考:Instrumentation#redefineClasses

常见问题

推荐使用 retransform 命令

  • redefine的class不能修改、添加、删除类的field和method,包括方法参数、方法名称及返回值
  • 如果mc失败,可以在本地开发环境编译好class文件,上传到目标系统,使用redefine热加载class
  • 目前redefine 和watch/trace/jad/tt等命令冲突,以后重新实现redefine功能会解决此问题

注意, redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field),参考jdk本身的文档。

reset命令对redefine的类无效。如果想重置,需要redefine原始的字节码。

redefine命令和jad/watch/trace/monitor/tt等命令会冲突。执行完redefine之后,如果再执行上面提到的命令,则会把redefine的字节码重置。 原因是jdk本身redefine和Retransform是不同的机制,同时使用两种机制来更新字节码,只有最后修改的会生效。

参数说明
参数名称 参数说明
[c:] ClassLoader的hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name

使用参考
  1. redefine /tmp/Test.class
  2. redefine -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
  3. redefine --classLoaderClass sun.misc.Launcher$AppClassLoader /tmp/Test.class /tmp/Test\$Inner.class

结合 jad/mc 命令使用
  1. jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
  2. mc /tmp/UserController.java -d /tmp
  3. redefine /tmp/com/example/demo/arthas/user/UserController.class
  • jad命令反编译,然后可以用其它编译器,比如vim来修改源码
  • mc命令来内存编译修改过的代码
  • 用redefine命令加载新的字节码

上传 .class 文件到服务器的技巧

使用mc命令来编译jad的反编译的代码有可能失败。可以在本地修改代码,编译好后再上传到服务器上。有的服务器不允许直接上传文件,可以使用base64命令来绕过。

  1. 在本地先转换.class文件为base64,再保存为result.txt
    1. base64 < Test.class > result.txt
  1. 到服务器上,新建并编辑result.txt,复制本地的内容,粘贴再保存

  2. 把服务器上的 result.txt还原为.class

    1. base64 -d < result.txt > Test.class
  1. 用md5命令计算哈希值,校验是否一致

redefine的限制
  • 不允许新增加field/method

  • 正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效

    1. public class MathGame {
    2. public static void main(String[] args) throws InterruptedException {
    3. MathGame game = new MathGame();
    4. while (true) {
    5. game.run();
    6. TimeUnit.SECONDS.sleep(1);
    7. // 这个不生效,因为代码一直跑在 while里
    8. System.out.println("in loop");
    9. }
    10. }
    11. public void run() throws InterruptedException {
    12. // 这个生效,因为run()函数每次都可以完整结束
    13. System.out.println("call run()");
    14. try {
    15. int number = random.nextInt();
    16. List<Integer> primeFactors = primeFactors(number);
    17. print(number, primeFactors);
    18. } catch (Exception e) {
    19. System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
    20. }
    21. }

dump

dump 已加载类的 bytecode 到特定目录

参数说明
参数名称 参数说明
class-pattern 类名表达式匹配
[c:] 类所属 ClassLoader 的 hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[d:] 设置类文件的目标目录
[E] 开启正则表达式匹配,默认为通配符匹配

使用参考
  1. $ dump java.lang.String
  2. HASHCODE CLASSLOADER LOCATION
  3. null /Users/admin/logs/arthas/classdump/java/lang/String.class
  4. Affect(row-cnt:1) cost in 119 ms.
  5. $ dump demo.*
  6. HASHCODE CLASSLOADER LOCATION
  7. 3d4eac69 +-sun.misc.Launcher$AppClassLoader@3d4eac69 /Users/admin/logs/arthas/classdump/sun.misc.Launcher$AppClassLoader-3d4eac69/demo/MathGame.class
  8. +-sun.misc.Launcher$ExtClassLoader@66350f69
  9. Affect(row-cnt:1) cost in 39 ms.
  10. $ dump -d /tmp/output java.lang.String
  11. HASHCODE CLASSLOADER LOCATION
  12. null /tmp/output/java/lang/String.class
  13. Affect(row-cnt:1) cost in 138 ms.
  • 指定classLoader

注意hashcode是变化的,需要先查看当前的ClassLoader信息,提取对应ClassLoader的hashcode。

如果你使用-c,你需要手动输入hashcode:-c <hashcode>

  1. $ dump -c 3d4eac69 demo.*

对于只有唯一实例的ClassLoader可以通过--classLoaderClass指定class name,使用起来更加方便:

  1. $ dump --classLoaderClass sun.misc.Launcher$AppClassLoader demo.*
  2. HASHCODE CLASSLOADER LOCATION
  3. 3d4eac69 +-sun.misc.Launcher$AppClassLoader@3d4eac69 /Users/admin/logs/arthas/classdump/sun.misc.Launcher$AppClassLoader-3d4eac69/demo/MathGame.class
  4. +-sun.misc.Launcher$ExtClassLoader@66350f69
  5. Affect(row-cnt:1) cost in 39 ms.
  • 注: 这里classLoaderClass 在 java 8 是 sun.misc.LauncherArthas - 图1AppClassLoader,katacoda目前环境是java8。

--classLoaderClass 的值是ClassLoader的类名,只有匹配到唯一的ClassLoader实例时才能工作,目的是方便输入通用命令,而-c <hashcode>是动态变化的。

classloader

查看classloader的继承树,urls,类加载信息

classloader 命令将 JVM 中所有的classloader的信息统计出来,并可以展示继承树,urls等。

可以让指定的classloader去getResources,打印出所有查找到的resources的url。对于ResourceNotFoundException比较有用。

参数说明
参数名称 参数说明
[l] 按类加载实例进行统计
[t] 打印所有ClassLoader的继承树
[a] 列出所有ClassLoader加载的类,请谨慎使用
[c:] ClassLoader的hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[c: r:] 用ClassLoader去查找resource
[c: load:] 用ClassLoader去加载指定的类

按类加载类型查看统计信息

  1. classloader

按类加载实例查看统计信息

  1. classloader -l

查看ClassLoader的继承树

  1. classloader -t

查看URLClassLoader实际的urls

  1. classloader -c 3d4eac69

注意 hashcode是变化的,需要先查看当前的ClassLoader信息,提取对应ClassLoader的hashcode。

对于只有唯一实例的ClassLoader可以通过class name指定,使用起来更加方便:

  1. $ classloader --classLoaderClass sun.misc.Launcher$AppClassLoader
  2. file:/private/tmp/arthas-demo.jar
  3. file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar
  4. Affect(row-cnt:9) cost in 3 ms.

使用ClassLoader去查找resource

  1. classloader -c 3d4eac69 -r META-INF/MANIFEST.MF

也可以尝试查找类的class文件:

  1. $ classloader -c 1b6d3586 -r java/lang/String.class
  2. jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/String.class

使用ClassLoader去加载类

  1. $ classloader -c 3d4eac69 --load demo.MathGame

monitor/watch/trace相关

请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 stop 或将增强过的类执行 reset 命令。

  • monitor——方法执行监控
  • watch——方法执行数据观测
  • trace——方法内部调用路径,并输出方法路径上的每个节点上耗时
  • stack——输出当前方法被调用的调用路径
  • tt——方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测

monitor

方法执行监控

对匹配 class-patternmethod-patterncondition-express的类、方法的调用进行监控。

monitor 命令是一个非实时返回命令.

实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。

服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何Arthas命令不会引起原有业务逻辑的改变。

监控的维度说明
监控项 说明
timestamp 时间戳
class Java类
method 方法(构造方法、普通方法)
total 调用次数
success 成功次数
fail 失败次数
rt 平均RT
fail-rate 失败率

参数说明

方法拥有一个命名参数 [c:],意思是统计周期(cycle of output),拥有一个整型的参数值

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
[E] 开启正则表达式匹配,默认为通配符匹配
[c:] 统计周期,默认值为120秒
[b] 方法调用之前计算condition-express

计算条件表达式过滤统计结果(方法执行完毕之后)

  1. monitor -c 5 demo.MathGame primeFactors "params[0] <= 2"

计算条件表达式过滤统计结果(方法执行完毕之前)

  1. monitor -b -c 5 com.test.testes.MathGame primeFactors "params[0] <= 2"

watch

方法执行数据观测

让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值抛出异常入参,通过编写 OGNL 表达式进行对应变量的查看。

参数说明

watch 的参数比较多,主要是因为它能在 4 个不同的场景观察对象

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
express 观察表达式
condition-express 条件表达式
[b] 方法调用之前观察
[e] 方法异常之后观察
[s] 方法返回之后观察
[f] 方法结束之后(正常返回和异常返回)观察
[E] 开启正则表达式匹配,默认为通配符匹配
[x:] 指定输出结果的属性遍历深度,默认为 1

这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。

观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。请参考表达式核心变量中关于该节点的描述。

特别说明

  • watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后
  • 4个观察事件点 -b-e-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
  • 这里要注意方法入参方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参
  • 当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在

观察方法出参和返回值
  1. watch demo.MathGame primeFactors "{params,returnObj}" -x 2

观察方法入参
  1. watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b

对比前一个例子,返回值为空(事件点为方法执行前,因此获取不到返回值)

同时观察方法调用前和方法返回后
  1. watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2

调整-x的值,观察具体的方法参数值
  1. watch demo.MathGame primeFactors "{params,target}" -x 3

-x表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1。

条件表达式的例子
  1. watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"

只有满足条件的调用,才会有响应。

观察异常信息的例子
  1. watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2
  • -e表示抛出异常时才触发
  • express中,表示异常信息的变量是throwExp

按照耗时进行过滤
  1. watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2

#cost>200(单位是ms)表示只有当耗时大于200ms时才会输出,过滤掉执行时间小于200ms的调用

观察当前对象中的属性

如果想查看方法运行前后,当前对象中的属性,可以使用target关键字,代表当前对象

  1. watch demo.MathGame primeFactors 'target'

然后使用target.field_name访问当前对象的某个属性
  1. watch demo.MathGame primeFactors 'target.illegalArgumentCount'

获取类的静态字段、调用类的静态方法的例子
  1. watch demo.MathGame * '{params,@demo.MathGame@random.nextInt(100)}' -n 1 -x 2

注意这里使用 Thread.currentThread().getContextClassLoader() 加载,使用精确classloader ognl更好。

watch/trace/monitor/stack/tt 命令都支持 --exclude-class-pattern 参数

使用 --exclude-class-pattern 参数可以排除掉指定的类,比如:

  1. watch javax.servlet.Filter * --exclude-class-pattern com.demo.TestFilter

排除掉指定的类

不匹配子类

默认情况下 watch/trace/monitor/stack/tt 命令都会匹配子类。如果想不匹配,可以通过全局参数关掉。

  1. options disable-sub-class true

使用 -v 参数打印更多信息

watch/trace/monitor/stack/tt 命令都支持 -v 参数

当命令执行之后,没有输出结果。有两种可能:

  1. 匹配到的函数没有被执行
  2. 条件表达式结果是 false

但用户区分不出是哪种情况。

使用 -v选项,则会打印Condition express的具体值和执行结果,方便确认。

比如:

  1. watch -v -x 2 demo.MathGame print 'params' 'params[0] > 100000'

trace

方法内部调用路径,并输出方法路径上的每个节点上耗时

trace 命令能主动搜索 class-patternmethod-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。

参数说明
参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
[E] 开启正则表达式匹配,默认为通配符匹配
[n:] 命令执行次数
#cost 方法执行耗时

这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。

观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。

trace次数限制

如果方法调用的次数很多,那么可以用-n参数指定捕捉结果的次数。比如下面的例子里,捕捉到一次调用就退出命令。

  1. trace demo.MathGame run -n 1

包含jdk的函数
  • --skipJDKMethod <value> skip jdk method trace, default value true.

默认情况下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数,需要显式设置--skipJDKMethod false

  1. trace --skipJDKMethod false demo.MathGame run

据调用耗时过滤
  1. trace demo.MathGame run '#cost > 10'

只会展示耗时大于10ms的调用路径,有助于在排查问题的时候,只关注异常情况

  • 是不是很眼熟,没错,在 JProfiler 等收费软件中你曾经见识类似的功能,这里你将可以通过命令就能打印出指定调用路径。 友情提醒下,trace 在执行的过程中本身是会有一定的性能开销,在统计的报告中并未像 JProfiler 一样预先减去其自身的统计开销。所以这统计出来有些许的不准,渲染路径上调用的类、方法越多,性能偏差越大。但还是能让你看清一些事情的。
  • [12.033735ms] 的含义,12.033735 的含义是:当前节点在当前步骤的耗时,单位为毫秒
  • [0,0,0ms,11]xxx:yyy() [throws Exception],对该方法中相同的方法调用进行了合并,0,0,0ms,11 表示方法调用耗时,min,max,total,countthrows Exception 表明该方法调用中存在异常返回
  • 这里存在一个统计不准确的问题,就是所有方法耗时加起来可能会小于该监测方法的总耗时,这个是由于 Arthas 本身的逻辑会有一定的耗时

trace多个类或者多个函数

trace命令只会trace匹配到的函数里的子调用,并不会向下trace多层。因为trace是代价比较贵的,多层trace可能会导致最终要trace的类和函数非常多。

可以用正则表匹配路径上的多个类和函数,一定程度上达到多层trace的效果。

  1. trace -E com.test.ClassA|org.test.ClassB method1|method2|method3

排除掉指定的类

使用 --exclude-class-pattern 参数可以排除掉指定的类,比如:

  1. trace javax.servlet.Filter * --exclude-class-pattern com.demo.TestFilter

动态trace

3.3.0 版本后支持。

打开终端1,trace上面demo里的run函数,可以看到打印出 listenerId: 1

  1. [arthas@59161]$ trace demo.MathGame run
  2. Press Q or Ctrl+C to abort.
  3. Affect(class count: 1 , method count: 1) cost in 112 ms, listenerId: 1
  4. `---ts=2020-07-09 16:48:11;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
  5. `---[1.389634ms] demo.MathGame:run()
  6. `---[0.123934ms] demo.MathGame:primeFactors() #24 [throws Exception]
  7. `---ts=2020-07-09 16:48:12;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
  8. `---[3.716391ms] demo.MathGame:run()
  9. +---[3.182813ms] demo.MathGame:primeFactors() #24
  10. `---[0.167786ms] demo.MathGame:print() #25

现在想要深入子函数primeFactors,可以打开一个新终端2,使用telnet localhost 3658连接上arthas,再trace primeFactors时,指定listenerId

  1. [arthas@59161]$ trace demo.MathGame primeFactors --listenerId 1
  2. Press Q or Ctrl+C to abort.
  3. Affect(class count: 1 , method count: 1) cost in 34 ms, listenerId: 1

这时终端2打印的结果,说明已经增强了一个函数:Affect(class count: 1 , method count: 1),但不再打印更多的结果。

再查看终端1,可以发现trace的结果增加了一层,打印了primeFactors函数里的内容:

  1. `---ts=2020-07-09 16:49:29;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
  2. `---[0.492551ms] demo.MathGame:run()
  3. `---[0.113929ms] demo.MathGame:primeFactors() #24 [throws Exception]
  4. `---[0.061462ms] demo.MathGame:primeFactors()
  5. `---[0.001018ms] throw:java.lang.IllegalArgumentException() #46
  6. `---ts=2020-07-09 16:49:30;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
  7. `---[0.409446ms] demo.MathGame:run()
  8. +---[0.232606ms] demo.MathGame:primeFactors() #24
  9. | `---[0.1294ms] demo.MathGame:primeFactors()
  10. `---[0.084025ms] demo.MathGame:print() #25

通过指定listenerId的方式动态trace,可以不断深入。另外 watch/tt/monitor等命令也支持类似的功能。

stack

输出当前方法被调用的调用路径

很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。

参数说明
参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
[E] 开启正则表达式匹配,默认为通配符匹配
[n:] 执行次数限制

这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。

观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。

stack
  1. stack demo.MathGame primeFactors

据条件表达式来过滤
  1. stack demo.MathGame primeFactors 'params[0]<0' -n 2

据执行时间来过滤
  1. stack demo.MathGame primeFactors '#cost>5'

tt

方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测

watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。

这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。

于是乎,TimeTunnel 命令就诞生了。

使用参考

记录调用

对于一个最基本的使用来说,就是记录下当前方法的每次调用环境现场。

  1. $ tt -t demo.MathGame primeFactors
  2. Press Ctrl+C to abort.
  3. Affect(class-cnt:1 , method-cnt:1) cost in 66 ms.
  4. INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD
  5. ----------------------------------------------------------------------------------------------------------------------
  6. 1000 2018-12-04 11:15:38 1.096236 false true 0x4b67cf4d MathGame primeFactors
  7. 1001 2018-12-04 11:15:39 0.191848 false true 0x4b67cf4d MathGame primeFactors
  8. 1002 2018-12-04 11:15:40 0.069523 false true 0x4b67cf4d MathGame primeFactors
  9. 1003 2018-12-04 11:15:41 0.186073 false true 0x4b67cf4d MathGame primeFactors
  10. 1004 2018-12-04 11:15:42 17.76437 true false 0x4b67cf4d MathGame primeFactors
  • 命令参数解析

    • -t
      tt 命令有很多个主参数,-t 就是其中之一。这个参数的表明希望记录下类 *Testprint 方法的每次执行情况。

    • -n 3
      当你执行一个调用量不高的方法时可能你还能有足够的时间用 CTRL+C 中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。
      此时你可以通过 -n 参数指定你需要记录的次数,当达到记录次数时 Arthas 会主动中断tt命令的记录过程,避免人工操作无法停止的情况。

  • 表格字段说明

表格字段 字段解释
INDEX 时间片段记录编号,每一个编号代表着一次调用,后续tt还有很多命令都是基于此编号指定记录操作,非常重要。
TIMESTAMP 方法执行的本机时间,记录了这个时间片段所发生的本机时间
COST(ms) 方法执行的耗时
IS-RET 方法是否以正常返回的形式结束
IS-EXP 方法是否以抛异常的形式结束
OBJECT 执行对象的hashCode(),注意,曾经有人误认为是对象在JVM中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体
CLASS 执行的类名
METHOD 执行的方法名
  • 条件表达式
    不知道大家是否有在使用过程中遇到以下困惑

    • Arthas 似乎很难区分出重载的方法
    • 我只需要观察特定参数,但是 tt 却全部都给我记录了下来

OGNL``Advice``tt``watch``trace``stack

  • 解决方法重载
    tt -t *Test print params.length==1
    通过制定参数个数的形式解决不同的方法签名,如果参数个数一样,你还可以这样写
    tt -t *Test print 'params[1] instanceof Integer'

  • 解决指定参数
    tt -t *Test print params[0].mobile=="13989838402"

  • 构成条件表达式的 Advice 对象
    前边看到了很多条件表达式中,都使用了 params[0],有关这个变量的介绍,请参考表达式核心变量

检索调用记录

当你用 tt 记录了一大片的时间片段之后,你希望能从中筛选出自己需要的时间片段,这个时候你就需要对现有记录进行检索。

假设我们有这些记录

  1. tt -l

我需要筛选出 primeFactors 方法的调用信息

  1. tt -s 'method.name=="primeFactors"'

你需要一个 -s 参数。同样的,搜索表达式的核心对象依旧是 Advice 对象。

查看调用信息

对于具体一个时间片的信息而言,你可以通过 -i 参数后边跟着对应的 INDEX 编号查看到他的详细信息。

  1. tt -i 1003

重做一次调用

当你稍稍做了一些调整之后,你可能需要前端系统重新触发一次你的调用,此时得求爷爷告奶奶的需要前端配合联调的同学再次发起一次调用。而有些场景下,这个调用不是这么好触发的。

tt 命令由于保存了当时调用的所有现场信息,所以我们可以自己主动对一个 INDEX 编号的时间片自主发起一次调用,从而解放你的沟通成本。此时你需要 -p 参数。通过 --replay-times 指定 调用次数,通过 --replay-interval 指定多次调用间隔(单位ms, 默认1000ms)

  1. tt -i 1004 -p

你会发现结果虽然一样,但调用的路径发生了变化,由原来的程序发起变成了 Arthas 自己的内部线程发起的调用了。

观察表达式

-w, --watch-express 观察时空隧道使用ognl 表达式

  • 使用表达式核心变量中所有变量作为已知条件编写表达式。
  1. tt -t demo.MathGame run -n 5
  • 获取类的静态字段、调用类的静态方法
  1. tt -t demo.MathGame run -n 5

注意这里使用 com.taobao.arthas.core.advisor.Advice#getLoader加载,使用精确classloader ognl更好。

高级用法 获取spring context 调用bean 方法

  • 需要强调的点

    1. ThreadLocal 信息丢失
      很多框架偷偷的将一些环境变量信息塞到了发起调用线程的 ThreadLocal 中,由于调用线程发生了变化,这些 ThreadLocal 线程信息无法通过 Arthas 保存,所以这些信息将会丢失。
      一些常见的 CASE 比如:鹰眼的 TraceId 等。

    2. 引用的对象
      需要强调的是,tt 命令是将当前环境的对象引用保存起来,但仅仅也只能保存一个引用而已。如果方法内部对入参进行了变更,或者返回的对象经过了后续的处理,那么在 tt 查看的时候将无法看到当时最准确的值。这也是为什么 watch 命令存在的意义。

管道

Arthas支持使用管道对上述命令的结果进行进一步的处理,如sm java.lang.String * | grep 'index'

  • grep——搜索满足条件的结果
  • plaintext——将命令的结果去除ANSI颜色
  • wc——按行统计输出结果

后台异步任务

当线上出现偶发的问题,比如需要watch某个条件,而这个条件一天可能才会出现一次时,异步后台任务就派上用场了

  • 使用 > 将结果重写向到日志文件,使用 & 指定命令是后台运行,session断开不影响任务执行(生命周期默认为1天)
  • jobs——列出所有job
  • kill——强制终止任务
  • fg——将暂停的任务拉到前台执行
  • bg——将暂停的任务放到后台执行

1. 使用&在后台执行任务

比如希望执行后台执行trace命令,那么调用下面命令

  1. trace Test t &

这时命令在后台执行,可以在console中继续执行其他命令。

2. 通过jobs查看任务

如果希望查看当前有哪些arthas任务在执行,可以执行jobs命令,执行结果如下

  1. $ jobs
  2. [10]*
  3. Stopped watch com.taobao.container.Test test "params[0].{? #this.name == null }" -x 2
  4. execution count : 19
  5. start time : Fri Sep 22 09:59:55 CST 2017
  6. timeout date : Sat Sep 23 09:59:55 CST 2017
  7. session : 3648e874-5e69-473f-9eed-7f89660b079b (current)

可以看到目前有一个后台任务在执行。

  • job id是10, * 表示此job是当前session创建
  • 状态是Stopped
  • execution count是执行次数,从启动开始已经执行了19次
  • timeout date是超时的时间,到这个时间,任务将会自动超时退出

3. 任务暂停和取消

当任务正在前台执行,比如直接调用命令trace Test t或者调用后台执行命令trace Test t &后又通过fg命令将任务转到前台。这时console中无法继续执行命令,但是可以接收并处理以下事件:

  • ‘ctrl + z’:将任务暂停。通过jbos查看任务状态将会变为Stopped,通过bg <job-id>或者fg <job-id>可让任务重新开始执行
  • ‘ctrl + c’:停止任务
  • ‘ctrl + d’:按照linux语义应当是退出终端,目前arthas中是空实现,不处理

4. fg、bg命令,将命令转到前台、后台继续执行

  • 任务在后台执行或者暂停状态(ctrl + z暂停任务)时,执行fg <job-id>将可以把对应的任务转到前台继续执行。在前台执行时,无法在console中执行其他命令
  • 当任务处于暂停状态时(ctrl + z暂停任务),执行bg <job-id>将可以把对应的任务在后台继续执行
  • 非当前session创建的job,只能由当前session fg到前台执行

5. 任务输出重定向

可通过>或者>>将任务输出结果输出到指定的文件中,可以和&一起使用,实现arthas命令的后台异步任务。比如:

  1. $ trace Test t >> test.out &

这时trace命令会在后台执行,并且把结果输出到~/logs/arthas-cache/test.out。可继续执行其他命令。并可查看文件中的命令执行结果。

当连接到远程的arthas server时,可能无法查看远程机器的文件,arthas同时支持了自动重定向到本地缓存路径。使用方法如下:

  1. $ trace Test t >> &
  2. job id : 2
  3. cache location : /Users/gehui/logs/arthas-cache/28198/2

可以看到并没有指定重定向文件位置,arthas自动重定向到缓存中了,执行命令后会输出job id和cache location。cache location就是重定向文件的路径,在系统logs目录下,路径包括pid和job id,避免和其他任务冲突。命令输出结果到/Users/gehui/logs/arthas-cache/28198/2中,job id为2。

6. 停止命令

异步执行的命令,如果希望停止,可执行kill

7. 其他

  • 最多同时支持8个命令使用重定向将结果写日志
  • 请勿同时开启过多的后台异步命令,以免对目标JVM性能造成影响
  • 如果不想停止arthas,继续执行后台任务,可以执行 quit 退出arthas控制台(stop 会停止arthas 服务)

ognl用法

查看第一个参数:

  1. watch com.taobao.container.Test test "params[0]"

查看第一个参数的size:

  1. watch com.taobao.container.Test test "params[0].size()"

将结果按name属性投影:

  1. watch com.taobao.container.Test test "params[0].{ #this.name }"

按条件过滤:

  1. watch com.taobao.container.Test test "params[0].{? #this.name == null }" -x 2

过滤后统计:

  1. watch com.taobao.container.Test test "params[0].{? #this.age > 10 }.size()" -x 2

子表达式求值:

  1. watch com.taobao.container.Test test "params[0].{? #this.age > 10 }.size().(#this > 20 ? #this - 10 : #this + 10)" -x 2

选择第一个满足条件:

  1. watch com.taobao.container.Test test "params[0].{^ #this.name != null}" -x 2

选择最后一个满足条件:

  1. watch com.taobao.container.Test test "params[0].{$ #this.name != null}" -x 2