Arthas-guide.md
... ...
@@ -0,0 +1,983 @@
1
+# Arthas - Java 线上问题定位处理的终极利器
2
+
3
+来源: https://www.wdbyte.com/2019/11/arthas/
4
+
5
+<div class="main" id="main">
6
+ <div class="post-time" style="font-size: .9em;">
7
+ <p>
8
+ <i class="fa fa-calendar"/> 最后更新日期:<span>2019-11-06</span>
9
+ </p>
10
+ </div>
11
+ <div class="post-toc" id="toc">
12
+ <div>
13
+ <b>目录</b>
14
+ </div>
15
+ <div>
16
+ <a style="margin: 0px;" href="#前言">前言</a>
17
+ </div>
18
+ <div>
19
+ <a style="margin: 0px;" href="#1arthas--介绍">1、Arthas 介绍</a>
20
+ </div>
21
+ <div>
22
+ <a style="margin: 0px;" href="#2arthas--使用场景">2、Arthas 使用场景</a>
23
+ </div>
24
+ <div>
25
+ <a style="margin: 0px;" href="#3arthas--怎么用">3、Arthas 怎么用</a>
26
+ </div>
27
+ <div>
28
+ <a style="margin: 20px;" href="#31-安装">3.1 安装</a>
29
+ </div>
30
+ <div>
31
+ <a style="margin: 20px;" href="#32-运行">3.2 运行</a>
32
+ </div>
33
+ <div>
34
+ <a style="margin: 20px;" href="#33-web-console">3.3 web console</a>
35
+ </div>
36
+ <div>
37
+ <a style="margin: 20px;" href="#34-常用命令">3.4 常用命令</a>
38
+ </div>
39
+ <div>
40
+ <a style="margin: 20px;" href="#35-退出">3.5 退出</a>
41
+ </div>
42
+ <div>
43
+ <a style="margin: 0px;" href="#4arthas-常用操作">4、Arthas 常用操作</a>
44
+ </div>
45
+ <div>
46
+ <a style="margin: 20px;" href="#41-全局监控">4.1 全局监控</a>
47
+ </div>
48
+ <div>
49
+ <a style="margin: 20px;" href="#42-cpu-为什么起飞了">4.2 CPU 为什么起飞了</a>
50
+ </div>
51
+ <div>
52
+ <a style="margin: 20px;" href="#43-线程池线程状态">4.3 线程池线程状态</a>
53
+ </div>
54
+ <div>
55
+ <a style="margin: 20px;" href="#44-线程死锁">4.4 线程死锁</a>
56
+ </div>
57
+ <div>
58
+ <a style="margin: 20px;" href="#45-反编译">4.5 反编译</a>
59
+ </div>
60
+ <div>
61
+ <a style="margin: 20px;" href="#46-查看字段信息">4.6 查看字段信息</a>
62
+ </div>
63
+ <div>
64
+ <a style="margin: 20px;" href="#47-查看方法信息">4.7 查看方法信息</a>
65
+ </div>
66
+ <div>
67
+ <a style="margin: 20px;" href="#48-对变量的值很是好奇">4.8 对变量的值很是好奇</a>
68
+ </div>
69
+ <div>
70
+ <a style="margin: 20px;" href="#49-程序有没有问题">4.9 程序有没有问题</a>
71
+ </div>
72
+ <div>
73
+ <a style="margin: 40px;" href="#491-运行较慢耗时较长">4.9.1 运行较慢、耗时较长</a>
74
+ </div>
75
+ <div>
76
+ <a style="margin: 40px;" href="#492-统计方法耗时">4.9.2 统计方法耗时</a>
77
+ </div>
78
+ <div>
79
+ <a style="margin: 20px;" href="#410-想观察方法信息">4.10 想观察方法信息</a>
80
+ </div>
81
+ <div>
82
+ <a style="margin: 40px;" href="#4101-观察方法的入参出参信息">4.10.1 观察方法的入参出参信息</a>
83
+ </div>
84
+ <div>
85
+ <a style="margin: 40px;" href="#4102-观察方法的调用路径">4.10.2 观察方法的调用路径</a>
86
+ </div>
87
+ <div>
88
+ <a style="margin: 40px;" href="#4103-方法调用时空隧道">4.10.3 方法调用时空隧道</a>
89
+ </div>
90
+ <div>
91
+ <a style="margin: 20px;" href="#45-火焰图分析">4.5. 火焰图分析</a>
92
+ </div>
93
+ <div>
94
+ <a style="margin: 40px;" href="#451使用案例">4.5.1.使用案例</a>
95
+ </div>
96
+ <div>
97
+ <a style="margin: 40px;" href="#452-复杂命令">4.5.2. 复杂命令</a>
98
+ </div>
99
+ </div>
100
+ <div class="post-content">
101
+ <p>
102
+ <img src="https://cdn.debug.group/git/2019/arthas-1572972116473.png" alt="Arthas logo"/>
103
+ </p>
104
+ <h1 id="前言">前言</h1>
105
+ <p>在使用 <strong>Arthas</strong> 之前,当遇到 Java 线上问题时,如 CPU 飙升、负载突高、内存溢出等问题,你需要查命令,查网络,然后 jps、jstack、jmap、jhat、jstat、hprof 等一通操作。最终焦头烂额,还不一定能查出问题所在。而现在,大多数的常见问题你都可以使用 <strong>Arthas</strong> 轻松定位,迅速解决,及时止损,准时下班。</p>
106
+ <p>
107
+ <img src="https://cdn.debug.group/img/23/02/Xnip2023-02-21_21-19-50.jpeg" alt=""/>
108
+ </p>
109
+ <h1 id="1arthas--介绍">1、Arthas 介绍</h1>
110
+ <p>
111
+ <strong>Arthas</strong> 是 <code>Alibaba</code> 在 2018 年 9 月开源的 <strong>Java 诊断</strong>工具。支持 <code>JDK6+</code>, 采用命令行交互模式,提供 <code>Tab</code> 自动补全,可以方便的定位和诊断线上程序运行问题。截至本篇文章编写时,已经收获 <code>Star</code> 17000+。</p>
112
+ <p>
113
+ <strong>Arthas</strong> 官方文档十分详细,本文也参考了官方文档内容,同时在开源在的 <code>Github</code> 的项目里的 <code>Issues</code> 里不仅有问题反馈,更有大量的使用案例,也可以进行学习参考。</p>
114
+ <p>开源地址:<em>https://github.com/alibaba/arthas</em>
115
+ </p>
116
+ <p>官方文档:<em>https://alibaba.github.io/arthas</em>
117
+ </p>
118
+ <!-- more -->
119
+ <h1 id="2arthas--使用场景">2、Arthas 使用场景</h1>
120
+ <p>得益于 <strong>Arthas</strong> 强大且丰富的功能,让 <strong>Arthas</strong> 能做的事情超乎想象。下面仅仅列举几项常见的使用情况,更多的使用场景可以在熟悉了 <strong>Arthas</strong> 之后自行探索。</p>
121
+ <ol>
122
+ <li>是否有一个全局视角来查看系统的运行状况?</li>
123
+ <li>为什么 CPU 又升高了,到底是哪里占用了 CPU ?</li>
124
+ <li>运行的多线程有死锁吗?有阻塞吗?</li>
125
+ <li>程序运行耗时很长,是哪里耗时比较长呢?如何监测呢?</li>
126
+ <li>这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?</li>
127
+ <li>我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?</li>
128
+ <li>遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?</li>
129
+ <li>有什么办法可以监控到 JVM 的实时运行状态?</li>
130
+ </ol>
131
+ <h1 id="3arthas--怎么用">3、Arthas 怎么用</h1>
132
+ <p>前文已经提到,<strong>Arthas</strong> 是一款命令行交互模式的 Java 诊断工具,由于是 Java 编写,所以可以直接下载相应 的 jar 包运行。</p>
133
+ <h2 id="31-安装">3.1 安装</h2>
134
+ <p>可以在官方 Github 上进行下载,如果速度较慢,可以尝试国内的码云 Gitee 下载。</p>
135
+ <pre>
136
+ <code class="language-shell"># github下载
137
+wget https://alibaba.github.io/arthas/arthas-boot.jar
138
+# 或者 Gitee 下载
139
+wget https://arthas.gitee.io/arthas-boot.jar
140
+# 打印帮助信息
141
+java -jar arthas-boot.jar -h
142
+</code>
143
+ </pre>
144
+ <h2 id="32-运行">3.2 运行</h2>
145
+ <p>
146
+ <strong>Arthas</strong> 只是一个 java 程序,所以可以直接用 <code>java -jar</code> 运行。运行时或者运行之后要选择要监测的 Java 进程。</p>
147
+ <pre>
148
+ <code class="language-shell"># 运行方式1,先运行,在选择 Java 进程 PID
149
+java -jar arthas-boot.jar
150
+# 选择进程(输入[]内编号(不是PID)回车)
151
+[INFO] arthas-boot version: 3.1.4
152
+[INFO] Found existing java process, please choose one and hit RETURN.
153
+* [1]: 11616 com.Arthas
154
+ [2]: 8676
155
+ [3]: 16200 org.jetbrains.jps.cmdline.Launcher
156
+ [4]: 21032 org.jetbrains.idea.maven.server.RemoteMavenServer
157
+
158
+# 运行方式2,运行时选择 Java 进程 PID
159
+java -jar arthas-boot.jar [PID]
160
+</code>
161
+ </pre>
162
+ <p>查看 PID 的方式可以通过 <code>ps</code> 命令,也可以通过 JDK 提供的 <code>jps</code>命令。</p>
163
+ <pre>
164
+ <code class="language-shell"># 查看运行的 java 进程信息
165
+$ jps -mlvV
166
+# 筛选 java 进程信息
167
+$ jps -mlvV | grep [xxx]
168
+</code>
169
+ </pre>
170
+ <p>
171
+ <code>jps</code> 筛选想要的进程方式。</p>
172
+ <p>
173
+ <img src="https://cdn.debug.group/git/2019/1570979767404.png" alt="jps 筛选进程"/>
174
+ </p>
175
+ <p>在出现 <strong>Arthas</strong> Logo 之后就可以使用命令进行问题诊断了。下面会详细介绍。</p>
176
+ <p>
177
+ <img src="https://cdn.debug.group/git/2019/image-20191106003512451.png" alt="Arthas 启动"/>
178
+ </p>
179
+ <p>更多的启动方式可以参考 help 帮助命令。</p>
180
+ <pre>
181
+ <code class="language-shell"># 其他用法
182
+EXAMPLES:
183
+ java -jar arthas-boot.jar &lt;pid&gt;
184
+ java -jar arthas-boot.jar --target-ip 0.0.0.0
185
+ java -jar arthas-boot.jar --telnet-port 9999 --http-port -1
186
+ java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
187
+ java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
188
+--agent-id bvDOe8XbTM2pQWjF4cfw
189
+ java -jar arthas-boot.jar --stat-url 'http://192.168.10.11:8080/api/stat'
190
+ java -jar arthas-boot.jar -c 'sysprop; thread' &lt;pid&gt;
191
+ java -jar arthas-boot.jar -f batch.as &lt;pid&gt;
192
+ java -jar arthas-boot.jar --use-version 3.1.4
193
+ java -jar arthas-boot.jar --versions
194
+ java -jar arthas-boot.jar --session-timeout 3600
195
+ java -jar arthas-boot.jar --attach-only
196
+ java -jar arthas-boot.jar --repo-mirror aliyun --use-http
197
+</code>
198
+ </pre>
199
+ <h2 id="33-web-console">3.3 web console</h2>
200
+ <p>
201
+ <strong>Arthas</strong> 目前支持 <code>Web Console</code>,在成功启动连接进程之后就已经自动启动,可以直接访问 http://127.0.0.1:8563/ 访问,页面上的操作模式和控制台完全一样。</p>
202
+ <p>
203
+ <img src="https://cdn.debug.group/git/2019/1570979937637.png" alt="1570979937637"/>
204
+ </p>
205
+ <h2 id="34-常用命令">3.4 常用命令</h2>
206
+ <p>下面列举一些 <a href="https://www.wdbyte.com/2019/11/arthas/" target="_blank">
207
+ <strong>Arthas</strong>
208
+ </a> 的常用命令,看到这里你可能还不知道怎么使用,别急,后面会一一介绍。</p>
209
+ <table class="ui celled table">
210
+ <thead>
211
+ <tr>
212
+ <th>命令</th>
213
+ <th>介绍</th>
214
+ </tr>
215
+ </thead>
216
+ <tbody>
217
+ <tr>
218
+ <td>
219
+ <a href="https://alibaba.github.io/arthas/dashboard.html" target="_blank">dashboard</a>
220
+ </td>
221
+ <td>当前系统的实时数据面板</td>
222
+ </tr>
223
+ <tr>
224
+ <td>
225
+ <a href="https://alibaba.github.io/arthas/thread.html" target="_blank">
226
+ <strong>thread</strong>
227
+ </a>
228
+ </td>
229
+ <td>查看当前 JVM 的线程堆栈信息</td>
230
+ </tr>
231
+ <tr>
232
+ <td>
233
+ <a href="https://alibaba.github.io/arthas/watch.html" target="_blank">
234
+ <strong>watch</strong>
235
+ </a>
236
+ </td>
237
+ <td>方法执行数据观测</td>
238
+ </tr>
239
+ <tr>
240
+ <td>
241
+ <strong>
242
+ <a href="https://alibaba.github.io/arthas/trace.html" target="_blank">trace</a>
243
+ </strong>
244
+ </td>
245
+ <td>方法内部调用路径,并输出方法路径上的每个节点上耗时</td>
246
+ </tr>
247
+ <tr>
248
+ <td>
249
+ <a href="https://alibaba.github.io/arthas/stack.html" target="_blank">
250
+ <strong>stack</strong>
251
+ </a>
252
+ </td>
253
+ <td>输出当前方法被调用的调用路径</td>
254
+ </tr>
255
+ <tr>
256
+ <td>
257
+ <a href="https://alibaba.github.io/arthas/tt.html" target="_blank">
258
+ <strong>tt</strong>
259
+ </a>
260
+ </td>
261
+ <td>方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测</td>
262
+ </tr>
263
+ <tr>
264
+ <td>
265
+ <a href="https://alibaba.github.io/arthas/monitor.html" target="_blank">monitor</a>
266
+ </td>
267
+ <td>方法执行监控</td>
268
+ </tr>
269
+ <tr>
270
+ <td>
271
+ <a href="https://alibaba.github.io/arthas/jvm.html" target="_blank">jvm</a>
272
+ </td>
273
+ <td>查看当前 JVM 信息</td>
274
+ </tr>
275
+ <tr>
276
+ <td>
277
+ <a href="https://alibaba.github.io/arthas/vmoption.html" target="_blank">vmoption</a>
278
+ </td>
279
+ <td>查看,更新 JVM 诊断相关的参数</td>
280
+ </tr>
281
+ <tr>
282
+ <td>
283
+ <a href="https://alibaba.github.io/arthas/sc.html" target="_blank">sc</a>
284
+ </td>
285
+ <td>查看 JVM 已加载的类信息</td>
286
+ </tr>
287
+ <tr>
288
+ <td>
289
+ <a href="https://alibaba.github.io/arthas/sm.html" target="_blank">sm</a>
290
+ </td>
291
+ <td>查看已加载类的方法信息</td>
292
+ </tr>
293
+ <tr>
294
+ <td>
295
+ <a href="https://alibaba.github.io/arthas/jad.html" target="_blank">jad</a>
296
+ </td>
297
+ <td>反编译指定已加载类的源码</td>
298
+ </tr>
299
+ <tr>
300
+ <td>
301
+ <a href="https://alibaba.github.io/arthas/classloader.html" target="_blank">classloader</a>
302
+ </td>
303
+ <td>查看 classloader 的继承树,urls,类加载信息</td>
304
+ </tr>
305
+ <tr>
306
+ <td>
307
+ <a href="https://alibaba.github.io/arthas/heapdump.html" target="_blank">heapdump</a>
308
+ </td>
309
+ <td>类似 jmap 命令的 heap dump 功能</td>
310
+ </tr>
311
+ </tbody>
312
+ </table>
313
+ <h2 id="35-退出">3.5 退出</h2>
314
+ <p>使用 shutdown 退出时 <strong>Arthas</strong> 同时自动重置所有增强过的类 。</p>
315
+ <h1 id="4arthas-常用操作">4、Arthas 常用操作</h1>
316
+ <p>上面已经了解了什么是 <strong>Arthas</strong>,以及 <strong>Arthas</strong> 的启动方式,下面会依据一些情况,详细说一说 <strong>Arthas</strong> 的使用方式。在使用命令的过程中如果有问题,每个命令都可以是 <code>-h</code> 查看帮助信息。</p>
317
+ <p>首先编写一个有各种情况的测试类运行起来,再使用 <strong>Arthas</strong> 进行问题定位,</p>
318
+ <pre>
319
+ <code class="language-java">import java.util.HashSet;
320
+import java.util.concurrent.ExecutorService;
321
+import java.util.concurrent.Executors;
322
+import lombok.extern.slf4j.Slf4j;
323
+
324
+/**
325
+ * &lt;p&gt;
326
+ * Arthas Demo
327
+ * 公众号:程序猿阿朗
328
+ *
329
+ * @Author niujinpeng
330
+ */
331
+@Slf4j
332
+public class Arthas {
333
+
334
+ private static HashSet hashSet = new HashSet();
335
+ /** 线程池,大小1*/
336
+ private static ExecutorService executorService = Executors.newFixedThreadPool(1);
337
+
338
+ public static void main(String[] args) {
339
+ // 模拟 CPU 过高,这里注释掉了,测试时可以打开
340
+ // cpu();
341
+ // 模拟线程阻塞
342
+ thread();
343
+ // 模拟线程死锁
344
+ deadThread();
345
+ // 不断的向 hashSet 集合增加数据
346
+ addHashSetThread();
347
+ }
348
+
349
+ /**
350
+ * 不断的向 hashSet 集合添加数据
351
+ */
352
+ public static void addHashSetThread() {
353
+ // 初始化常量
354
+ new Thread(() -&gt; {
355
+ int count = 0;
356
+ while (true) {
357
+ try {
358
+ hashSet.add(&quot;count&quot; + count);
359
+ Thread.sleep(10000);
360
+ count++;
361
+ } catch (InterruptedException e) {
362
+ e.printStackTrace();
363
+ }
364
+ }
365
+ }).start();
366
+ }
367
+
368
+ public static void cpu() {
369
+ cpuHigh();
370
+ cpuNormal();
371
+ }
372
+
373
+ /**
374
+ * 极度消耗CPU的线程
375
+ */
376
+ private static void cpuHigh() {
377
+ Thread thread = new Thread(() -&gt; {
378
+ while (true) {
379
+ log.info(&quot;cpu start 100&quot;);
380
+ }
381
+ });
382
+ // 添加到线程
383
+ executorService.submit(thread);
384
+ }
385
+
386
+ /**
387
+ * 普通消耗CPU的线程
388
+ */
389
+ private static void cpuNormal() {
390
+ for (int i = 0; i &lt; 10; i++) {
391
+ new Thread(() -&gt; {
392
+ while (true) {
393
+ log.info(&quot;cpu start&quot;);
394
+ try {
395
+ Thread.sleep(3000);
396
+ } catch (InterruptedException e) {
397
+ e.printStackTrace();
398
+ }
399
+ }
400
+ }).start();
401
+ }
402
+ }
403
+
404
+ /**
405
+ * 模拟线程阻塞,向已经满了的线程池提交线程
406
+ */
407
+ private static void thread() {
408
+ Thread thread = new Thread(() -&gt; {
409
+ while (true) {
410
+ log.debug(&quot;thread start&quot;);
411
+ try {
412
+ Thread.sleep(3000);
413
+ } catch (InterruptedException e) {
414
+ e.printStackTrace();
415
+ }
416
+ }
417
+ });
418
+ // 添加到线程
419
+ executorService.submit(thread);
420
+ }
421
+
422
+ /**
423
+ * 死锁
424
+ */
425
+ private static void deadThread() {
426
+ /** 创建资源 */
427
+ Object resourceA = new Object();
428
+ Object resourceB = new Object();
429
+ // 创建线程
430
+ Thread threadA = new Thread(() -&gt; {
431
+ synchronized (resourceA) {
432
+ log.info(Thread.currentThread() + &quot; get ResourceA&quot;);
433
+ try {
434
+ Thread.sleep(1000);
435
+ } catch (InterruptedException e) {
436
+ e.printStackTrace();
437
+ }
438
+ log.info(Thread.currentThread() + &quot;waiting get resourceB&quot;);
439
+ synchronized (resourceB) {
440
+ log.info(Thread.currentThread() + &quot; get resourceB&quot;);
441
+ }
442
+ }
443
+ });
444
+
445
+ Thread threadB = new Thread(() -&gt; {
446
+ synchronized (resourceB) {
447
+ log.info(Thread.currentThread() + &quot; get ResourceB&quot;);
448
+ try {
449
+ Thread.sleep(1000);
450
+ } catch (InterruptedException e) {
451
+ e.printStackTrace();
452
+ }
453
+ log.info(Thread.currentThread() + &quot;waiting get resourceA&quot;);
454
+ synchronized (resourceA) {
455
+ log.info(Thread.currentThread() + &quot; get resourceA&quot;);
456
+ }
457
+ }
458
+ });
459
+ threadA.start();
460
+ threadB.start();
461
+ }
462
+}
463
+</code>
464
+ </pre>
465
+ <h2 id="41-全局监控">4.1 全局监控</h2>
466
+ <p>使用 <strong>dashboard</strong> 命令可以概览程序的 线程、内存、GC、运行环境信息。</p>
467
+ <p>
468
+ <img src="https://cdn.debug.group/git/2019/1571212470373.png" alt="dashboard"/>
469
+ </p>
470
+ <h2 id="42-cpu-为什么起飞了">4.2 CPU 为什么起飞了</h2>
471
+ <p>上面的代码例子有一个 <code>CPU</code> 空转的死循环,非常的消耗 <code>CPU性能</code>,那么怎么找出来呢?</p>
472
+ <p>使用 <strong>thread</strong>查看<strong>所有</strong>线程信息,同时会列出每个线程的 <code>CPU</code> 使用率,可以看到图里 ID 为12 的线程 CPU 使用100%。
473
+<img src="https://cdn.debug.group/git/2019/1570983440457.png" alt=""/>
474
+ </p>
475
+ <p>使用命令 <strong>thread 12</strong> 查看 CPU 消耗较高的 12 号线程信息,可以看到 CPU 使用较高的方法和行数(这里的行数可能和上面代码里的行数有区别,因为上面的代码在我写文章时候重新排过版了)。</p>
476
+ <p>
477
+ <img src="https://cdn.debug.group/git/2019/1570983401254.png" alt=""/>
478
+ </p>
479
+ <p>上面是先通过观察总体的线程信息,然后查看具体的线程运行情况。如果只是为了寻找 CPU 使用较高的线程,可以直接使用命令 <strong>thread -n [显示的线程个数]</strong> ,就可以排列出 CPU 使用率 <strong>Top N</strong> 的线程。</p>
480
+ <p>
481
+ <img src="https://cdn.debug.group/git/2019/1570983061047.png" alt=""/>
482
+ </p>
483
+ <p>定位到的 CPU 使用最高的方法。</p>
484
+ <p>
485
+ <img src="https://cdn.debug.group/git/2019/1571016675083.png" alt=""/>
486
+ </p>
487
+ <h2 id="43-线程池线程状态">4.3 线程池线程状态</h2>
488
+ <p>定位线程问题之前,先回顾一下线程的几种常见状态:</p>
489
+ <ul>
490
+ <li>
491
+ <strong>RUNNABLE</strong> 运行中</li>
492
+ <li>
493
+ <strong>TIMED_WAITIN</strong> 调用了以下方法的线程会进入<strong>TIMED_WAITING</strong>:
494
+<ol>
495
+ <li>Thread#sleep()</li>
496
+ <li>Object#wait() 并加了超时参数</li>
497
+ <li>Thread#join() 并加了超时参数</li>
498
+ <li>LockSupport#parkNanos()</li>
499
+ <li>LockSupport#parkUntil()</li>
500
+ </ol>
501
+ </li>
502
+ <li>
503
+ <strong>WAITING</strong> 当线程调用以下方法时会进入WAITING状态:
504
+<ol>
505
+ <li>Object#wait() 而且不加超时参数</li>
506
+ <li>Thread#join() 而且不加超时参数</li>
507
+ <li>LockSupport#park()</li>
508
+ </ol>
509
+ </li>
510
+ <li>
511
+ <strong>BLOCKED</strong> 阻塞,等待锁</li>
512
+ </ul>
513
+ <p>上面的模拟代码里,定义了线程池大小为1 的线程池,然后在 <code>cpuHigh</code> 方法里提交了一个线程,在 <code>thread</code>方法再次提交了一个线程,后面的这个线程因为线程池已满,会阻塞下来。</p>
514
+ <p>使用 <strong>thread | grep pool</strong> 命令查看线程池里线程信息。</p>
515
+ <p>
516
+ <img src="https://cdn.debug.group/git/2019/1571020871537.png" alt=""/>
517
+ </p>
518
+ <p>可以看到线程池有 <strong>WAITING</strong> 的线程。</p>
519
+ <p>
520
+ <img src="https://cdn.debug.group/git/2019/1571021838323.png" alt=""/>
521
+ </p>
522
+ <h2 id="44-线程死锁">4.4 线程死锁</h2>
523
+ <p>上面的模拟代码里 <code>deadThread </code>方法实现了一个死锁,使用 <strong>thread -b</strong> 命令查看直接定位到死锁信息。</p>
524
+ <pre>
525
+ <code class="language-java">/**
526
+ * 死锁
527
+ */
528
+private static void deadThread() {
529
+ /** 创建资源 */
530
+ Object resourceA = new Object();
531
+ Object resourceB = new Object();
532
+ // 创建线程
533
+ Thread threadA = new Thread(() -&gt; {
534
+ synchronized (resourceA) {
535
+ log.info(Thread.currentThread() + &quot; get ResourceA&quot;);
536
+ try {
537
+ Thread.sleep(1000);
538
+ } catch (InterruptedException e) {
539
+ e.printStackTrace();
540
+ }
541
+ log.info(Thread.currentThread() + &quot;waiting get resourceB&quot;);
542
+ synchronized (resourceB) {
543
+ log.info(Thread.currentThread() + &quot; get resourceB&quot;);
544
+ }
545
+ }
546
+ });
547
+
548
+ Thread threadB = new Thread(() -&gt; {
549
+ synchronized (resourceB) {
550
+ log.info(Thread.currentThread() + &quot; get ResourceB&quot;);
551
+ try {
552
+ Thread.sleep(1000);
553
+ } catch (InterruptedException e) {
554
+ e.printStackTrace();
555
+ }
556
+ log.info(Thread.currentThread() + &quot;waiting get resourceA&quot;);
557
+ synchronized (resourceA) {
558
+ log.info(Thread.currentThread() + &quot; get resourceA&quot;);
559
+ }
560
+ }
561
+ });
562
+ threadA.start();
563
+ threadB.start();
564
+}
565
+</code>
566
+ </pre>
567
+ <p>检查到的死锁信息。</p>
568
+ <p>
569
+ <img src="https://cdn.debug.group/git/2019/1571206638142.png" alt=""/>
570
+ </p>
571
+ <h2 id="45-反编译">4.5 反编译</h2>
572
+ <p>上面的代码放到了包 <code>com</code>下,假设这是一个线程环境,当怀疑当前运行的代码不是自己想要的代码时,可以直接反编译出代码,也可以选择性的查看类的字段或方法信息。</p>
573
+ <p>如果怀疑不是自己的代码,可以使用 <strong>jad</strong> 命令直接反编译 class。</p>
574
+ <p>
575
+ <img src="https://cdn.debug.group/git/2019/image-20191106012005747.png" alt="jad"/>
576
+ </p>
577
+ <p>
578
+ <code>jad</code> 命令还提供了一些其他参数:</p>
579
+ <pre>
580
+ <code class="language-shell"># 反编译只显示源码
581
+jad --source-only com.Arthas
582
+# 反编译某个类的某个方法
583
+jad --source-only com.Arthas mysql
584
+</code>
585
+ </pre>
586
+ <h2 id="46-查看字段信息">4.6 查看字段信息</h2>
587
+ <p>使用 **sc -d -f ** 命令查看类的字段信息。</p>
588
+ <pre>
589
+ <code class="language-shell">[arthas@20252]$ sc -d -f com.Arthas
590
+sc -d -f com.Arthas
591
+ class-info com.Arthas
592
+ code-source /C:/Users/Niu/Desktop/arthas/target/classes/
593
+ name com.Arthas
594
+ isInterface false
595
+ isAnnotation false
596
+ isEnum false
597
+ isAnonymousClass false
598
+ isArray false
599
+ isLocalClass false
600
+ isMemberClass false
601
+ isPrimitive false
602
+ isSynthetic false
603
+ simple-name Arthas
604
+ modifier public
605
+ annotation
606
+ interfaces
607
+ super-class +-java.lang.Object
608
+ class-loader +-sun.misc.Launcher$AppClassLoader@18b4aac2
609
+ +-sun.misc.Launcher$ExtClassLoader@2ef1e4fa
610
+ classLoaderHash 18b4aac2
611
+ fields modifierfinal,private,static
612
+ type org.slf4j.Logger
613
+ name log
614
+ value Logger[com.Arthas]
615
+
616
+ modifierprivate,static
617
+ type java.util.HashSet
618
+ name hashSet
619
+ value [count1, count2]
620
+
621
+ modifierprivate,static
622
+ type java.util.concurrent.ExecutorService
623
+ name executorService
624
+ value java.util.concurrent.ThreadPoolExecutor@71c03156[Ru
625
+ nning, pool size = 1, active threads = 1, queued ta
626
+ sks = 0, completed tasks = 0]
627
+
628
+
629
+Affect(row-cnt:1) cost in 9 ms.
630
+</code>
631
+ </pre>
632
+ <h2 id="47-查看方法信息">4.7 查看方法信息</h2>
633
+ <p>使用 <strong>sm</strong> 命令查看类的方法信息。</p>
634
+ <pre>
635
+ <code class="language-shell">[arthas@22180]$ sm com.Arthas
636
+com.Arthas &lt;init&gt;()V
637
+com.Arthas start()V
638
+com.Arthas thread()V
639
+com.Arthas deadThread()V
640
+com.Arthas lambda$cpuHigh$1()V
641
+com.Arthas cpuHigh()V
642
+com.Arthas lambda$thread$3()V
643
+com.Arthas addHashSetThread()V
644
+com.Arthas cpuNormal()V
645
+com.Arthas cpu()V
646
+com.Arthas lambda$addHashSetThread$0()V
647
+com.Arthas lambda$deadThread$4(Ljava/lang/Object;Ljava/lang/Object;)V
648
+com.Arthas lambda$deadThread$5(Ljava/lang/Object;Ljava/lang/Object;)V
649
+com.Arthas lambda$cpuNormal$2()V
650
+Affect(row-cnt:16) cost in 6 ms.
651
+</code>
652
+ </pre>
653
+ <h2 id="48-对变量的值很是好奇">4.8 对变量的值很是好奇</h2>
654
+ <p>使用 <strong>ognl</strong> 命令,ognl 表达式可以轻松操作想要的信息。</p>
655
+ <p>代码还是上面的示例代码,我们查看变量 <code>hashSet</code> 中的数据:</p>
656
+ <p>
657
+ <img src="https://cdn.debug.group/git/2019/1571196786678.png" alt=""/>
658
+ </p>
659
+ <p>查看静态变量 <code>hashSet</code> 信息。</p>
660
+ <pre>
661
+ <code class="language-shell">[arthas@19856]$ ognl '@com.Arthas@hashSet'
662
+@HashSet[
663
+ @String[count1],
664
+ @String[count2],
665
+ @String[count29],
666
+ @String[count28],
667
+ @String[count0],
668
+ @String[count27],
669
+ @String[count5],
670
+ @String[count26],
671
+ @String[count6],
672
+ @String[count25],
673
+ @String[count3],
674
+ @String[count24],
675
+</code>
676
+ </pre>
677
+ <p>查看静态变量 hashSet 大小。</p>
678
+ <pre>
679
+ <code class="language-shell">[arthas@19856]$ ognl '@<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dfbcb0b2f19eadabb7beac9fb7beacb78cbaabf1acb6a5ba">[email&#160;protected]</a>()'
680
+ @Integer[57]
681
+</code>
682
+ </pre>
683
+ <p>甚至可以进行操作。</p>
684
+ <pre>
685
+ <code class="language-shell">[arthas@19856]$ ognl '@<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="01626e6c2f40737569607241696072695264752f606565">[email&#160;protected]</a>(&quot;test&quot;)'
686
+ @Boolean[true]
687
+[arthas@19856]$
688
+# 查看添加的字符
689
+[arthas@19856]$ ognl '@com.Arthas@hashSet' | grep test
690
+ @String[test],
691
+[arthas@19856]$
692
+</code>
693
+ </pre>
694
+ <p>
695
+ <code>ognl</code> 可以做很多事情,可以参考 <a href="https://github.com/alibaba/arthas/issues/71" target="_blank">ognl 表达式特殊用法( https://github.com/alibaba/arthas/issues/71 )</a>。</p>
696
+ <h2 id="49-程序有没有问题">4.9 程序有没有问题</h2>
697
+ <h3 id="491-运行较慢耗时较长">4.9.1 运行较慢、耗时较长</h3>
698
+ <p>使用 <strong>trace</strong> 命令可以跟踪统计方法耗时</p>
699
+ <p>这次换一个模拟代码。一个最基础的 Springboot 项目(当然,不想 Springboot 的话,你也可以直接在 UserController 里 main 方法启动)控制层 <code>getUser</code> 方法调用了 <code>userService.get(uid);</code>,这个方法中分别进行<code>check</code>、<code>service</code>、<code>redis</code>、<code>mysql</code>操作。</p>
700
+ <pre>
701
+ <code class="language-java">@RestController
702
+@Slf4j
703
+public class UserController {
704
+
705
+ @Autowired
706
+ private UserServiceImpl userService;
707
+
708
+ @GetMapping(value = &quot;/user&quot;)
709
+ public HashMap&lt;String, Object&gt; getUser(Integer uid) throws Exception {
710
+ // 模拟用户查询
711
+ userService.get(uid);
712
+ HashMap&lt;String, Object&gt; hashMap = new HashMap&lt;&gt;();
713
+ hashMap.put(&quot;uid&quot;, uid);
714
+ hashMap.put(&quot;name&quot;, &quot;name&quot; + uid);
715
+ return hashMap;
716
+ }
717
+}
718
+</code>
719
+ </pre>
720
+ <p>模拟代码 Service:</p>
721
+ <pre>
722
+ <code class="language-java">@Service
723
+@Slf4j
724
+public class UserServiceImpl {
725
+
726
+ public void get(Integer uid) throws Exception {
727
+ check(uid);
728
+ service(uid);
729
+ redis(uid);
730
+ mysql(uid);
731
+ }
732
+
733
+ public void service(Integer uid) throws Exception {
734
+ int count = 0;
735
+ for (int i = 0; i &lt; 10; i++) {
736
+ count += i;
737
+ }
738
+ log.info(&quot;service end {}&quot;, count);
739
+ }
740
+
741
+ public void redis(Integer uid) throws Exception {
742
+ int count = 0;
743
+ for (int i = 0; i &lt; 10000; i++) {
744
+ count += i;
745
+ }
746
+ log.info(&quot;redis end {}&quot;, count);
747
+ }
748
+
749
+ public void mysql(Integer uid) throws Exception {
750
+ long count = 0;
751
+ for (int i = 0; i &lt; 10000000; i++) {
752
+ count += i;
753
+ }
754
+ log.info(&quot;mysql end {}&quot;, count);
755
+ }
756
+
757
+ public boolean check(Integer uid) throws Exception {
758
+ if (uid == null || uid &lt; 0) {
759
+ log.error(&quot;uid不正确,uid:{}&quot;, uid);
760
+ throw new Exception(&quot;uid不正确&quot;);
761
+ }
762
+ return true;
763
+ }
764
+}
765
+
766
+</code>
767
+ </pre>
768
+ <p>运行 Springboot 之后,使用 **trace== ** 命令开始检测耗时情况。</p>
769
+ <pre>
770
+ <code class="language-shell">[arthas@6592]$ trace com.UserController getUser
771
+</code>
772
+ </pre>
773
+ <p>访问接口 <code>/getUser</code> ,可以看到耗时信息,看到 <code>com.UserServiceImpl:get() </code>方法耗时较高。
774
+<img src="https://cdn.debug.group/git/2019/1571208153793.png" alt=""/>
775
+ </p>
776
+ <p>继续跟踪耗时高的方法,然后再次访问。</p>
777
+ <pre>
778
+ <code class="language-shell">[arthas@6592]$ trace com.UserServiceImpl get
779
+</code>
780
+ </pre>
781
+ <p>
782
+ <img src="https://cdn.debug.group/git/2019/1571208245597.png" alt=""/>
783
+ </p>
784
+ <p>很清楚的看到是 <code>com.UserServiceImpl </code>的 <code>mysql </code>方法耗时是最高的。</p>
785
+ <pre>
786
+ <code class="language-java">Affect(class-cnt:1 , method-cnt:1) cost in 31 ms.
787
+`---ts=2019-10-16 14:40:10;thread_name=http-nio-8080-exec-8;id=1f;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@23a918c7
788
+ `---[6.792201ms] com.UserServiceImpl:get()
789
+ +---[0.008ms] com.UserServiceImpl:check() #17
790
+ +---[0.076ms] com.UserServiceImpl:service() #18
791
+ +---[0.1089ms] com.UserServiceImpl:redis() #19
792
+ `---[6.528899ms] com.UserServiceImpl:mysql() #20
793
+</code>
794
+ </pre>
795
+ <h3 id="492-统计方法耗时">4.9.2 统计方法耗时</h3>
796
+ <p>使用 <strong>monitor</strong> 命令监控统计方法的执行情况。</p>
797
+ <p>每5秒统计一次 <code>com.UserServiceImpl</code> 类的 <code>get</code> 方法执行情况。</p>
798
+ <pre>
799
+ <code class="language-shell">monitor -c 5 com.UserServiceImpl get
800
+</code>
801
+ </pre>
802
+ <p>
803
+ <img src="https://cdn.debug.group/git/2019/1571210158018.png" alt=""/>
804
+ </p>
805
+ <h2 id="410-想观察方法信息">4.10 想观察方法信息</h2>
806
+ <p>下面的示例用到了文章的前两个模拟代码。</p>
807
+ <h3 id="4101-观察方法的入参出参信息">4.10.1 观察方法的入参出参信息</h3>
808
+ <p>使用 <strong>watch</strong> 命令轻松查看输入输出参数以及异常等信息。</p>
809
+ <pre>
810
+ <code class="language-shell"> USAGE:
811
+ watch [-b] [-e] [-x &lt;value&gt;] [-f] [-h] [-n &lt;value&gt;] [-E] [-M &lt;value&gt;] [-s] class-pattern method-pattern express [condition-express]
812
+
813
+ SUMMARY:
814
+ Display the input/output parameter, return object, and thrown exception of specified method invocation
815
+ The express may be one of the following expression (evaluated dynamically):
816
+ target : the object
817
+ clazz : the object's class
818
+ method : the constructor or method
819
+ params : the parameters array of method
820
+ params[0..n] : the element of parameters array
821
+ returnObj : the returned object of method
822
+ throwExp : the throw exception of method
823
+ isReturn : the method ended by return
824
+ isThrow : the method ended by throwing exception
825
+ #cost : the execution time in ms of method invocation
826
+ Examples:
827
+ watch -b org.apache.commons.lang.StringUtils isBlank params
828
+ watch -f org.apache.commons.lang.StringUtils isBlank returnObj
829
+ watch org.apache.commons.lang.StringUtils isBlank '{params, target, returnObj}' -x 2
830
+ watch -bf *StringUtils isBlank params
831
+ watch *StringUtils isBlank params[0]
832
+ watch *StringUtils isBlank params[0] params[0].length==1
833
+ watch *StringUtils isBlank params '#cost&gt;100'
834
+ watch -E -b org\.apache\.commons\.lang\.StringUtils isBlank params[0]
835
+
836
+ WIKI:
837
+ https://alibaba.github.io/arthas/watch
838
+</code>
839
+ </pre>
840
+ <p>常用操作:</p>
841
+ <pre>
842
+ <code class="language-shell"># 查看入参和出参
843
+$ watch com.Arthas addHashSet '{params[0],returnObj}'
844
+# 查看入参和出参大小
845
+$ watch com.Arthas addHashSet '{params[0],returnObj.size}'
846
+# 查看入参和出参中是否包含 'count10'
847
+$ watch com.Arthas addHashSet '{params[0],returnObj.contains(&quot;count10&quot;)}'
848
+# 查看入参和出参,出参 toString
849
+$ watch com.Arthas addHashSet '{params[0],returnObj.toString()}'
850
+</code>
851
+ </pre>
852
+ <p>查看入参出参。</p>
853
+ <p>
854
+ <img src="https://cdn.debug.group/git/2019/1571196483469.png" alt=""/>
855
+ </p>
856
+ <p>查看返回的异常信息。</p>
857
+ <h3 id="4102-观察方法的调用路径">4.10.2 观察方法的调用路径</h3>
858
+ <p>使用 <strong>stack</strong>命令查看方法的调用信息。</p>
859
+ <pre>
860
+ <code class="language-shell"># 观察 类com.UserServiceImpl的 mysql 方法调用路径
861
+stack com.UserServiceImpl mysql
862
+</code>
863
+ </pre>
864
+ <p>
865
+ <img src="https://cdn.debug.group/git/2019/1571210706602.png" alt=""/>
866
+ </p>
867
+ <h3 id="4103-方法调用时空隧道">4.10.3 方法调用时空隧道</h3>
868
+ <p>使用 <strong>tt</strong> 命令记录方法执行的详细情况。</p>
869
+ <blockquote>
870
+ <p>
871
+ <strong>tt</strong> 命令方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 。</p>
872
+ </blockquote>
873
+ <p>常用操作:</p>
874
+ <p>开始记录方法调用信息:tt -t com.UserServiceImpl check</p>
875
+ <p>
876
+ <img src="https://cdn.debug.group/git/2019/1571212007249.png" alt=""/>
877
+ </p>
878
+ <p>可以看到记录中 INDEX=1001 的记录的 IS-EXP = true ,说明这次调用出现异常。</p>
879
+ <p>查看记录的方法调用信息: tt -l</p>
880
+ <p>
881
+ <img src="https://cdn.debug.group/git/2019/1571212080071.png" alt=""/>
882
+ </p>
883
+ <p>查看调用记录的详细信息(-i 指定 INDEX): tt -i 1001</p>
884
+ <p>
885
+ <img src="https://cdn.debug.group/git/2019/1571212151064.png" alt=""/>
886
+ </p>
887
+ <p>可以看到 INDEX=1001 的记录的异常信息。</p>
888
+ <p>重新发起调用,使用指定记录,使用 -p 重新调用。</p>
889
+ <pre>
890
+ <code class="language-java">tt -i 1001 -p
891
+</code>
892
+ </pre>
893
+ <p>
894
+ <img src="https://cdn.debug.group/git/2019/1571212227058.png" alt=""/>
895
+ </p>
896
+ <h2 id="45-火焰图分析">4.5. 火焰图分析</h2>
897
+ <p>最近 Arthas 性能分析工具上线了<strong>火焰图</strong>分析功能,Arthas 使用 <strong>async-profiler</strong> 生成 CPU/内存火焰图进行性能分析,弥补了之前内存分析的不足。在 Arthas 上使用还是比较方便的。</p>
898
+ <p>
899
+ <strong>
900
+ <code>profiler</code>
901
+ </strong> 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。</p>
902
+ <p>
903
+ <strong>
904
+ <code>profiler</code>
905
+ </strong> 命令基本运行结构是 <strong>
906
+ <code>profiler action [actionArg]</code>
907
+ </strong>
908
+ </p>
909
+ <h3 id="451使用案例">
910
+ <strong>4.5.1.使用案例</strong>
911
+ </h3>
912
+ <p>
913
+ <strong>开启 prifilter</strong>
914
+ </p>
915
+ <p>默认情况下,生成的是cpu的火焰图,即event为<code>cpu</code>。可以用<code>--event</code>参数来指定,使用 start 命令开始捕获信息。</p>
916
+ <pre>
917
+ <code class="language-shell">$ profiler start
918
+Started [cpu] profiling
919
+</code>
920
+ </pre>
921
+ <p>获取已采集的sample的数量</p>
922
+ <pre>
923
+ <code class="language-shell">$ profiler getSamples
924
+23
925
+</code>
926
+ </pre>
927
+ <p>查看 profiler状态,可以查看当前 profiler 在采样哪种 <code>event </code>和进行的采样时间。</p>
928
+ <pre>
929
+ <code class="language-shell">$ profiler status
930
+
931
+[cpu] profiling is running for 4 seconds
932
+</code>
933
+ </pre>
934
+ <p>
935
+ <strong>停止profiler</strong>
936
+ </p>
937
+ <p>生成svg格式火焰图</p>
938
+ <pre>
939
+ <code class="language-shell">$ profiler stop
940
+profiler output file: /tmp/demo/arthas-output/20191125-135546.svg
941
+OK
942
+</code>
943
+ </pre>
944
+ <p>默认情况下,生成的结果保存到应用的<code>工作目录</code>下的<code>arthas-output</code>目录。可以通过 <code>--file</code>参数来指定输出结果路径。</p>
945
+ <p>比如:</p>
946
+ <pre>
947
+ <code class="language-shell">$ profiler stop --file /tmp/output.svg
948
+</code>
949
+ </pre>
950
+ <p>
951
+ <strong>HTML 格式输出</strong>
952
+ </p>
953
+ <p>默认情况下,结果文件是<code>svg</code>格式,如果想生成<code>html</code>格式,可以用<code>--format</code>参数指定:$ profiler stop --format html</p>
954
+ <p>
955
+ <strong>查看 profilter</strong>
956
+ </p>
957
+ <p>默认情况下,arthas使用3658端口,则可以打开: http://localhost:3658/arthas-output/ 查看到<code>arthas-output</code>目录下面的profiler结果:</p>
958
+ <p>
959
+ <img src="https://cdn.debug.group/img/23/10/151226645.webp" alt=""/>
960
+ </p>
961
+ <p>点击可以查看具体的结果:<strong>火焰图里,横条越长,代表使用的越多,从下到上是调用堆栈信息</strong>
962
+ </p>
963
+ <p>
964
+ <img src="https://cdn.debug.group/img/23/10/151303153.webp" alt=""/>
965
+ </p>
966
+ <p>**profilter 自持多种分析方式,**常见的有 event: cpu|alloc|lock|cache-misses etc. 比如要分析内存使用情况。</p>
967
+ <p>$ profiler start --event alloc</p>
968
+ <h3 id="452-复杂命令">4.5.2. 复杂命令</h3>
969
+ <p>比如开始采样:</p>
970
+ <pre>
971
+ <code class="language-shell">profiler execute 'start'
972
+</code>
973
+ </pre>
974
+ <p>停止采样,并保存到指定文件里:</p>
975
+ <pre>
976
+ <code class="language-shell">profiler execute 'stop,file=/tmp/result.svg'
977
+</code>
978
+ </pre>
979
+ <p>文中代码已经上传到 <a href="https://github.com/niumoo/lab-notes/" target="_blank">Github</a>。</p>
980
+ </div>
981
+</div>
982
+
983
+
Arthas-guide.mediawiki
... ...
@@ -1,744 +0,0 @@
1
-
2
-来源: https://www.wdbyte.com/2019/11/arthas/
3
-
4
-<div class="main" id="main">
5
- <div class="post-title"><h1>Arthas - Java 线上问题定位处理的终极利器</h1></div>
6
- <div class="post-time" style="font-size: .9em;">
7
- <p><i class="fa fa-calendar"></i> 最后更新日期:<span>2019-11-06</span> | <a href="#" onclick="showToc()" id="tocBtn"><i class="fa fa-pencil"></i> 显示目录</a> |
8
- 字体 <a href="#" onclick="changeFontSize(1)" title="增大字体" style="background: white">➕</a>
9
- <a href="#" onclick="changeFontSize(-1)" title="减小字体" style="background: white">➖</a>
10
- </p>
11
- </div>
12
- <div class="post-toc" id="toc">
13
- <div><b>目录</b></div>
14
- <div><a style="margin: 0px;" href="#前言">前言</a></div>
15
- <div><a style="margin: 0px;" href="#1arthas--介绍">1、Arthas 介绍</a></div>
16
- <div><a style="margin: 0px;" href="#2arthas--使用场景">2、Arthas 使用场景</a></div>
17
- <div><a style="margin: 0px;" href="#3arthas--怎么用">3、Arthas 怎么用</a></div>
18
- <div><a style="margin: 20px;" href="#31-安装">3.1 安装</a></div>
19
- <div><a style="margin: 20px;" href="#32-运行">3.2 运行</a></div>
20
- <div><a style="margin: 20px;" href="#33-web-console">3.3 web console</a></div>
21
- <div><a style="margin: 20px;" href="#34-常用命令">3.4 常用命令</a></div>
22
- <div><a style="margin: 20px;" href="#35-退出">3.5 退出</a></div>
23
- <div><a style="margin: 0px;" href="#4arthas-常用操作">4、Arthas 常用操作</a></div>
24
- <div><a style="margin: 20px;" href="#41-全局监控">4.1 全局监控</a></div>
25
- <div><a style="margin: 20px;" href="#42-cpu-为什么起飞了">4.2 CPU 为什么起飞了</a></div>
26
- <div><a style="margin: 20px;" href="#43-线程池线程状态">4.3 线程池线程状态</a></div>
27
- <div><a style="margin: 20px;" href="#44-线程死锁">4.4 线程死锁</a></div>
28
- <div><a style="margin: 20px;" href="#45-反编译">4.5 反编译</a></div>
29
- <div><a style="margin: 20px;" href="#46-查看字段信息">4.6 查看字段信息</a></div>
30
- <div><a style="margin: 20px;" href="#47-查看方法信息">4.7 查看方法信息</a></div>
31
- <div><a style="margin: 20px;" href="#48-对变量的值很是好奇">4.8 对变量的值很是好奇</a></div>
32
- <div><a style="margin: 20px;" href="#49-程序有没有问题">4.9 程序有没有问题</a></div>
33
- <div><a style="margin: 40px;" href="#491-运行较慢耗时较长">4.9.1 运行较慢、耗时较长</a></div>
34
- <div><a style="margin: 40px;" href="#492-统计方法耗时">4.9.2 统计方法耗时</a></div>
35
- <div><a style="margin: 20px;" href="#410-想观察方法信息">4.10 想观察方法信息</a></div>
36
- <div><a style="margin: 40px;" href="#4101-观察方法的入参出参信息">4.10.1 观察方法的入参出参信息</a></div>
37
- <div><a style="margin: 40px;" href="#4102-观察方法的调用路径">4.10.2 观察方法的调用路径</a></div>
38
- <div><a style="margin: 40px;" href="#4103-方法调用时空隧道">4.10.3 方法调用时空隧道</a></div>
39
- <div><a style="margin: 20px;" href="#45-火焰图分析">4.5. 火焰图分析</a></div>
40
- <div><a style="margin: 40px;" href="#451使用案例">4.5.1.使用案例</a></div>
41
- <div><a style="margin: 40px;" href="#452-复杂命令">4.5.2. 复杂命令</a></div>
42
- </div>
43
- <div class="post-content"><p><img src="https://cdn.debug.group/git/2019/arthas-1572972116473.png" alt="Arthas logo" /></p>
44
-<h1 id="前言">前言</h1>
45
-<p>在使用 <strong>Arthas</strong> 之前,当遇到 Java 线上问题时,如 CPU 飙升、负载突高、内存溢出等问题,你需要查命令,查网络,然后 jps、jstack、jmap、jhat、jstat、hprof 等一通操作。最终焦头烂额,还不一定能查出问题所在。而现在,大多数的常见问题你都可以使用 <strong>Arthas</strong> 轻松定位,迅速解决,及时止损,准时下班。</p>
46
-<p><img src="https://cdn.debug.group/img/23/02/Xnip2023-02-21_21-19-50.jpeg" alt="" /></p>
47
-<h1 id="1arthas--介绍">1、Arthas 介绍</h1>
48
-<p><strong>Arthas</strong> 是 <code>Alibaba</code> 在 2018 年 9 月开源的 <strong>Java 诊断</strong>工具。支持 <code>JDK6+</code>, 采用命令行交互模式,提供 <code>Tab</code> 自动补全,可以方便的定位和诊断线上程序运行问题。截至本篇文章编写时,已经收获 <code>Star</code> 17000+。</p>
49
-<p><strong>Arthas</strong> 官方文档十分详细,本文也参考了官方文档内容,同时在开源在的 <code>Github</code> 的项目里的 <code>Issues</code> 里不仅有问题反馈,更有大量的使用案例,也可以进行学习参考。</p>
50
-<p>开源地址:<em>https://github.com/alibaba/arthas</em></p>
51
-<p>官方文档:<em>https://alibaba.github.io/arthas</em></p>
52
-<!-- more -->
53
-<h1 id="2arthas--使用场景">2、Arthas 使用场景</h1>
54
-<p>得益于 <strong>Arthas</strong> 强大且丰富的功能,让 <strong>Arthas</strong> 能做的事情超乎想象。下面仅仅列举几项常见的使用情况,更多的使用场景可以在熟悉了 <strong>Arthas</strong> 之后自行探索。</p>
55
-<ol>
56
-<li>是否有一个全局视角来查看系统的运行状况?</li>
57
-<li>为什么 CPU 又升高了,到底是哪里占用了 CPU ?</li>
58
-<li>运行的多线程有死锁吗?有阻塞吗?</li>
59
-<li>程序运行耗时很长,是哪里耗时比较长呢?如何监测呢?</li>
60
-<li>这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?</li>
61
-<li>我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?</li>
62
-<li>遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?</li>
63
-<li>有什么办法可以监控到 JVM 的实时运行状态?</li>
64
-</ol>
65
-<h1 id="3arthas--怎么用">3、Arthas 怎么用</h1>
66
-<p>前文已经提到,<strong>Arthas</strong> 是一款命令行交互模式的 Java 诊断工具,由于是 Java 编写,所以可以直接下载相应 的 jar 包运行。</p>
67
-<h2 id="31-安装">3.1 安装</h2>
68
-<p>可以在官方 Github 上进行下载,如果速度较慢,可以尝试国内的码云 Gitee 下载。</p>
69
-<pre><code class="language-shell"># github下载
70
-wget https://alibaba.github.io/arthas/arthas-boot.jar
71
-# 或者 Gitee 下载
72
-wget https://arthas.gitee.io/arthas-boot.jar
73
-# 打印帮助信息
74
-java -jar arthas-boot.jar -h
75
-</code></pre>
76
-<h2 id="32-运行">3.2 运行</h2>
77
-<p><strong>Arthas</strong> 只是一个 java 程序,所以可以直接用 <code>java -jar</code> 运行。运行时或者运行之后要选择要监测的 Java 进程。</p>
78
-<pre><code class="language-shell"># 运行方式1,先运行,在选择 Java 进程 PID
79
-java -jar arthas-boot.jar
80
-# 选择进程(输入[]内编号(不是PID)回车)
81
-[INFO] arthas-boot version: 3.1.4
82
-[INFO] Found existing java process, please choose one and hit RETURN.
83
-* [1]: 11616 com.Arthas
84
- [2]: 8676
85
- [3]: 16200 org.jetbrains.jps.cmdline.Launcher
86
- [4]: 21032 org.jetbrains.idea.maven.server.RemoteMavenServer
87
-
88
-# 运行方式2,运行时选择 Java 进程 PID
89
-java -jar arthas-boot.jar [PID]
90
-</code></pre>
91
-<p>查看 PID 的方式可以通过 <code>ps</code> 命令,也可以通过 JDK 提供的 <code>jps</code>命令。</p>
92
-<pre><code class="language-shell"># 查看运行的 java 进程信息
93
-$ jps -mlvV
94
-# 筛选 java 进程信息
95
-$ jps -mlvV | grep [xxx]
96
-</code></pre>
97
-<p><code>jps</code> 筛选想要的进程方式。</p>
98
-<p><img src="https://cdn.debug.group/git/2019/1570979767404.png" alt="jps 筛选进程" /></p>
99
-<p>在出现 <strong>Arthas</strong> Logo 之后就可以使用命令进行问题诊断了。下面会详细介绍。</p>
100
-<p><img src="https://cdn.debug.group/git/2019/image-20191106003512451.png" alt="Arthas 启动" /></p>
101
-<p>更多的启动方式可以参考 help 帮助命令。</p>
102
-<pre><code class="language-shell"># 其他用法
103
-EXAMPLES:
104
- java -jar arthas-boot.jar &lt;pid&gt;
105
- java -jar arthas-boot.jar --target-ip 0.0.0.0
106
- java -jar arthas-boot.jar --telnet-port 9999 --http-port -1
107
- java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
108
- java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
109
---agent-id bvDOe8XbTM2pQWjF4cfw
110
- java -jar arthas-boot.jar --stat-url 'http://192.168.10.11:8080/api/stat'
111
- java -jar arthas-boot.jar -c 'sysprop; thread' &lt;pid&gt;
112
- java -jar arthas-boot.jar -f batch.as &lt;pid&gt;
113
- java -jar arthas-boot.jar --use-version 3.1.4
114
- java -jar arthas-boot.jar --versions
115
- java -jar arthas-boot.jar --session-timeout 3600
116
- java -jar arthas-boot.jar --attach-only
117
- java -jar arthas-boot.jar --repo-mirror aliyun --use-http
118
-</code></pre>
119
-<h2 id="33-web-console">3.3 web console</h2>
120
-<p><strong>Arthas</strong> 目前支持 <code>Web Console</code>,在成功启动连接进程之后就已经自动启动,可以直接访问 http://127.0.0.1:8563/ 访问,页面上的操作模式和控制台完全一样。</p>
121
-<p><img src="https://cdn.debug.group/git/2019/1570979937637.png" alt="1570979937637" /></p>
122
-<h2 id="34-常用命令">3.4 常用命令</h2>
123
-<p>下面列举一些 <a href="https://www.wdbyte.com/2019/11/arthas/" target="_blank"><strong>Arthas</strong></a> 的常用命令,看到这里你可能还不知道怎么使用,别急,后面会一一介绍。</p>
124
-<table class="ui celled table">
125
-<thead>
126
-<tr>
127
-<th>命令</th>
128
-<th>介绍</th>
129
-</tr>
130
-</thead>
131
-<tbody>
132
-<tr>
133
-<td><a href="https://alibaba.github.io/arthas/dashboard.html" target="_blank">dashboard</a></td>
134
-<td>当前系统的实时数据面板</td>
135
-</tr>
136
-<tr>
137
-<td><a href="https://alibaba.github.io/arthas/thread.html" target="_blank"><strong>thread</strong></a></td>
138
-<td>查看当前 JVM 的线程堆栈信息</td>
139
-</tr>
140
-<tr>
141
-<td><a href="https://alibaba.github.io/arthas/watch.html" target="_blank"><strong>watch</strong></a></td>
142
-<td>方法执行数据观测</td>
143
-</tr>
144
-<tr>
145
-<td><strong><a href="https://alibaba.github.io/arthas/trace.html" target="_blank">trace</a></strong></td>
146
-<td>方法内部调用路径,并输出方法路径上的每个节点上耗时</td>
147
-</tr>
148
-<tr>
149
-<td><a href="https://alibaba.github.io/arthas/stack.html" target="_blank"><strong>stack</strong></a></td>
150
-<td>输出当前方法被调用的调用路径</td>
151
-</tr>
152
-<tr>
153
-<td><a href="https://alibaba.github.io/arthas/tt.html" target="_blank"><strong>tt</strong></a></td>
154
-<td>方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测</td>
155
-</tr>
156
-<tr>
157
-<td><a href="https://alibaba.github.io/arthas/monitor.html" target="_blank">monitor</a></td>
158
-<td>方法执行监控</td>
159
-</tr>
160
-<tr>
161
-<td><a href="https://alibaba.github.io/arthas/jvm.html" target="_blank">jvm</a></td>
162
-<td>查看当前 JVM 信息</td>
163
-</tr>
164
-<tr>
165
-<td><a href="https://alibaba.github.io/arthas/vmoption.html" target="_blank">vmoption</a></td>
166
-<td>查看,更新 JVM 诊断相关的参数</td>
167
-</tr>
168
-<tr>
169
-<td><a href="https://alibaba.github.io/arthas/sc.html" target="_blank">sc</a></td>
170
-<td>查看 JVM 已加载的类信息</td>
171
-</tr>
172
-<tr>
173
-<td><a href="https://alibaba.github.io/arthas/sm.html" target="_blank">sm</a></td>
174
-<td>查看已加载类的方法信息</td>
175
-</tr>
176
-<tr>
177
-<td><a href="https://alibaba.github.io/arthas/jad.html" target="_blank">jad</a></td>
178
-<td>反编译指定已加载类的源码</td>
179
-</tr>
180
-<tr>
181
-<td><a href="https://alibaba.github.io/arthas/classloader.html" target="_blank">classloader</a></td>
182
-<td>查看 classloader 的继承树,urls,类加载信息</td>
183
-</tr>
184
-<tr>
185
-<td><a href="https://alibaba.github.io/arthas/heapdump.html" target="_blank">heapdump</a></td>
186
-<td>类似 jmap 命令的 heap dump 功能</td>
187
-</tr>
188
-</tbody>
189
-</table>
190
-<h2 id="35-退出">3.5 退出</h2>
191
-<p>使用 shutdown 退出时 <strong>Arthas</strong> 同时自动重置所有增强过的类 。</p>
192
-<h1 id="4arthas-常用操作">4、Arthas 常用操作</h1>
193
-<p>上面已经了解了什么是 <strong>Arthas</strong>,以及 <strong>Arthas</strong> 的启动方式,下面会依据一些情况,详细说一说 <strong>Arthas</strong> 的使用方式。在使用命令的过程中如果有问题,每个命令都可以是 <code>-h</code> 查看帮助信息。</p>
194
-<p>首先编写一个有各种情况的测试类运行起来,再使用 <strong>Arthas</strong> 进行问题定位,</p>
195
-<pre><code class="language-java">import java.util.HashSet;
196
-import java.util.concurrent.ExecutorService;
197
-import java.util.concurrent.Executors;
198
-import lombok.extern.slf4j.Slf4j;
199
-
200
-/**
201
- * &lt;p&gt;
202
- * Arthas Demo
203
- * 公众号:程序猿阿朗
204
- *
205
- * @Author niujinpeng
206
- */
207
-@Slf4j
208
-public class Arthas {
209
-
210
- private static HashSet hashSet = new HashSet();
211
- /** 线程池,大小1*/
212
- private static ExecutorService executorService = Executors.newFixedThreadPool(1);
213
-
214
- public static void main(String[] args) {
215
- // 模拟 CPU 过高,这里注释掉了,测试时可以打开
216
- // cpu();
217
- // 模拟线程阻塞
218
- thread();
219
- // 模拟线程死锁
220
- deadThread();
221
- // 不断的向 hashSet 集合增加数据
222
- addHashSetThread();
223
- }
224
-
225
- /**
226
- * 不断的向 hashSet 集合添加数据
227
- */
228
- public static void addHashSetThread() {
229
- // 初始化常量
230
- new Thread(() -&gt; {
231
- int count = 0;
232
- while (true) {
233
- try {
234
- hashSet.add(&quot;count&quot; + count);
235
- Thread.sleep(10000);
236
- count++;
237
- } catch (InterruptedException e) {
238
- e.printStackTrace();
239
- }
240
- }
241
- }).start();
242
- }
243
-
244
- public static void cpu() {
245
- cpuHigh();
246
- cpuNormal();
247
- }
248
-
249
- /**
250
- * 极度消耗CPU的线程
251
- */
252
- private static void cpuHigh() {
253
- Thread thread = new Thread(() -&gt; {
254
- while (true) {
255
- log.info(&quot;cpu start 100&quot;);
256
- }
257
- });
258
- // 添加到线程
259
- executorService.submit(thread);
260
- }
261
-
262
- /**
263
- * 普通消耗CPU的线程
264
- */
265
- private static void cpuNormal() {
266
- for (int i = 0; i &lt; 10; i++) {
267
- new Thread(() -&gt; {
268
- while (true) {
269
- log.info(&quot;cpu start&quot;);
270
- try {
271
- Thread.sleep(3000);
272
- } catch (InterruptedException e) {
273
- e.printStackTrace();
274
- }
275
- }
276
- }).start();
277
- }
278
- }
279
-
280
- /**
281
- * 模拟线程阻塞,向已经满了的线程池提交线程
282
- */
283
- private static void thread() {
284
- Thread thread = new Thread(() -&gt; {
285
- while (true) {
286
- log.debug(&quot;thread start&quot;);
287
- try {
288
- Thread.sleep(3000);
289
- } catch (InterruptedException e) {
290
- e.printStackTrace();
291
- }
292
- }
293
- });
294
- // 添加到线程
295
- executorService.submit(thread);
296
- }
297
-
298
- /**
299
- * 死锁
300
- */
301
- private static void deadThread() {
302
- /** 创建资源 */
303
- Object resourceA = new Object();
304
- Object resourceB = new Object();
305
- // 创建线程
306
- Thread threadA = new Thread(() -&gt; {
307
- synchronized (resourceA) {
308
- log.info(Thread.currentThread() + &quot; get ResourceA&quot;);
309
- try {
310
- Thread.sleep(1000);
311
- } catch (InterruptedException e) {
312
- e.printStackTrace();
313
- }
314
- log.info(Thread.currentThread() + &quot;waiting get resourceB&quot;);
315
- synchronized (resourceB) {
316
- log.info(Thread.currentThread() + &quot; get resourceB&quot;);
317
- }
318
- }
319
- });
320
-
321
- Thread threadB = new Thread(() -&gt; {
322
- synchronized (resourceB) {
323
- log.info(Thread.currentThread() + &quot; get ResourceB&quot;);
324
- try {
325
- Thread.sleep(1000);
326
- } catch (InterruptedException e) {
327
- e.printStackTrace();
328
- }
329
- log.info(Thread.currentThread() + &quot;waiting get resourceA&quot;);
330
- synchronized (resourceA) {
331
- log.info(Thread.currentThread() + &quot; get resourceA&quot;);
332
- }
333
- }
334
- });
335
- threadA.start();
336
- threadB.start();
337
- }
338
-}
339
-</code></pre>
340
-<h2 id="41-全局监控">4.1 全局监控</h2>
341
-<p>使用 <strong>dashboard</strong> 命令可以概览程序的 线程、内存、GC、运行环境信息。</p>
342
-<p><img src="https://cdn.debug.group/git/2019/1571212470373.png" alt="dashboard" /></p>
343
-<h2 id="42-cpu-为什么起飞了">4.2 CPU 为什么起飞了</h2>
344
-<p>上面的代码例子有一个 <code>CPU</code> 空转的死循环,非常的消耗 <code>CPU性能</code>,那么怎么找出来呢?</p>
345
-<p>使用 <strong>thread</strong>查看<strong>所有</strong>线程信息,同时会列出每个线程的 <code>CPU</code> 使用率,可以看到图里 ID 为12 的线程 CPU 使用100%。
346
-<img src="https://cdn.debug.group/git/2019/1570983440457.png" alt="" /></p>
347
-<p>使用命令 <strong>thread 12</strong> 查看 CPU 消耗较高的 12 号线程信息,可以看到 CPU 使用较高的方法和行数(这里的行数可能和上面代码里的行数有区别,因为上面的代码在我写文章时候重新排过版了)。</p>
348
-<p><img src="https://cdn.debug.group/git/2019/1570983401254.png" alt="" /></p>
349
-<p>上面是先通过观察总体的线程信息,然后查看具体的线程运行情况。如果只是为了寻找 CPU 使用较高的线程,可以直接使用命令 <strong>thread -n [显示的线程个数]</strong> ,就可以排列出 CPU 使用率 <strong>Top N</strong> 的线程。</p>
350
-<p><img src="https://cdn.debug.group/git/2019/1570983061047.png" alt="" /></p>
351
-<p>定位到的 CPU 使用最高的方法。</p>
352
-<p><img src="https://cdn.debug.group/git/2019/1571016675083.png" alt="" /></p>
353
-<h2 id="43-线程池线程状态">4.3 线程池线程状态</h2>
354
-<p>定位线程问题之前,先回顾一下线程的几种常见状态:</p>
355
-<ul>
356
-<li><strong>RUNNABLE</strong> 运行中</li>
357
-<li><strong>TIMED_WAITIN</strong> 调用了以下方法的线程会进入<strong>TIMED_WAITING</strong>:
358
-<ol>
359
-<li>Thread#sleep()</li>
360
-<li>Object#wait() 并加了超时参数</li>
361
-<li>Thread#join() 并加了超时参数</li>
362
-<li>LockSupport#parkNanos()</li>
363
-<li>LockSupport#parkUntil()</li>
364
-</ol>
365
-</li>
366
-<li><strong>WAITING</strong> 当线程调用以下方法时会进入WAITING状态:
367
-<ol>
368
-<li>Object#wait() 而且不加超时参数</li>
369
-<li>Thread#join() 而且不加超时参数</li>
370
-<li>LockSupport#park()</li>
371
-</ol>
372
-</li>
373
-<li><strong>BLOCKED</strong> 阻塞,等待锁</li>
374
-</ul>
375
-<p>上面的模拟代码里,定义了线程池大小为1 的线程池,然后在 <code>cpuHigh</code> 方法里提交了一个线程,在 <code>thread</code>方法再次提交了一个线程,后面的这个线程因为线程池已满,会阻塞下来。</p>
376
-<p>使用 <strong>thread | grep pool</strong> 命令查看线程池里线程信息。</p>
377
-<p><img src="https://cdn.debug.group/git/2019/1571020871537.png" alt="" /></p>
378
-<p>可以看到线程池有 <strong>WAITING</strong> 的线程。</p>
379
-<p><img src="https://cdn.debug.group/git/2019/1571021838323.png" alt="" /></p>
380
-<h2 id="44-线程死锁">4.4 线程死锁</h2>
381
-<p>上面的模拟代码里 <code>deadThread </code>方法实现了一个死锁,使用 <strong>thread -b</strong> 命令查看直接定位到死锁信息。</p>
382
-<pre><code class="language-java">/**
383
- * 死锁
384
- */
385
-private static void deadThread() {
386
- /** 创建资源 */
387
- Object resourceA = new Object();
388
- Object resourceB = new Object();
389
- // 创建线程
390
- Thread threadA = new Thread(() -&gt; {
391
- synchronized (resourceA) {
392
- log.info(Thread.currentThread() + &quot; get ResourceA&quot;);
393
- try {
394
- Thread.sleep(1000);
395
- } catch (InterruptedException e) {
396
- e.printStackTrace();
397
- }
398
- log.info(Thread.currentThread() + &quot;waiting get resourceB&quot;);
399
- synchronized (resourceB) {
400
- log.info(Thread.currentThread() + &quot; get resourceB&quot;);
401
- }
402
- }
403
- });
404
-
405
- Thread threadB = new Thread(() -&gt; {
406
- synchronized (resourceB) {
407
- log.info(Thread.currentThread() + &quot; get ResourceB&quot;);
408
- try {
409
- Thread.sleep(1000);
410
- } catch (InterruptedException e) {
411
- e.printStackTrace();
412
- }
413
- log.info(Thread.currentThread() + &quot;waiting get resourceA&quot;);
414
- synchronized (resourceA) {
415
- log.info(Thread.currentThread() + &quot; get resourceA&quot;);
416
- }
417
- }
418
- });
419
- threadA.start();
420
- threadB.start();
421
-}
422
-</code></pre>
423
-<p>检查到的死锁信息。</p>
424
-<p><img src="https://cdn.debug.group/git/2019/1571206638142.png" alt="" /></p>
425
-<h2 id="45-反编译">4.5 反编译</h2>
426
-<p>上面的代码放到了包 <code>com</code>下,假设这是一个线程环境,当怀疑当前运行的代码不是自己想要的代码时,可以直接反编译出代码,也可以选择性的查看类的字段或方法信息。</p>
427
-<p>如果怀疑不是自己的代码,可以使用 <strong>jad</strong> 命令直接反编译 class。</p>
428
-<p><img src="https://cdn.debug.group/git/2019/image-20191106012005747.png" alt="jad" /></p>
429
-<p><code>jad</code> 命令还提供了一些其他参数:</p>
430
-<pre><code class="language-shell"># 反编译只显示源码
431
-jad --source-only com.Arthas
432
-# 反编译某个类的某个方法
433
-jad --source-only com.Arthas mysql
434
-</code></pre>
435
-<h2 id="46-查看字段信息">4.6 查看字段信息</h2>
436
-<p>使用 **sc -d -f ** 命令查看类的字段信息。</p>
437
-<pre><code class="language-shell">[arthas@20252]$ sc -d -f com.Arthas
438
-sc -d -f com.Arthas
439
- class-info com.Arthas
440
- code-source /C:/Users/Niu/Desktop/arthas/target/classes/
441
- name com.Arthas
442
- isInterface false
443
- isAnnotation false
444
- isEnum false
445
- isAnonymousClass false
446
- isArray false
447
- isLocalClass false
448
- isMemberClass false
449
- isPrimitive false
450
- isSynthetic false
451
- simple-name Arthas
452
- modifier public
453
- annotation
454
- interfaces
455
- super-class +-java.lang.Object
456
- class-loader +-sun.misc.Launcher$AppClassLoader@18b4aac2
457
- +-sun.misc.Launcher$ExtClassLoader@2ef1e4fa
458
- classLoaderHash 18b4aac2
459
- fields modifierfinal,private,static
460
- type org.slf4j.Logger
461
- name log
462
- value Logger[com.Arthas]
463
-
464
- modifierprivate,static
465
- type java.util.HashSet
466
- name hashSet
467
- value [count1, count2]
468
-
469
- modifierprivate,static
470
- type java.util.concurrent.ExecutorService
471
- name executorService
472
- value java.util.concurrent.ThreadPoolExecutor@71c03156[Ru
473
- nning, pool size = 1, active threads = 1, queued ta
474
- sks = 0, completed tasks = 0]
475
-
476
-
477
-Affect(row-cnt:1) cost in 9 ms.
478
-</code></pre>
479
-<h2 id="47-查看方法信息">4.7 查看方法信息</h2>
480
-<p>使用 <strong>sm</strong> 命令查看类的方法信息。</p>
481
-<pre><code class="language-shell">[arthas@22180]$ sm com.Arthas
482
-com.Arthas &lt;init&gt;()V
483
-com.Arthas start()V
484
-com.Arthas thread()V
485
-com.Arthas deadThread()V
486
-com.Arthas lambda$cpuHigh$1()V
487
-com.Arthas cpuHigh()V
488
-com.Arthas lambda$thread$3()V
489
-com.Arthas addHashSetThread()V
490
-com.Arthas cpuNormal()V
491
-com.Arthas cpu()V
492
-com.Arthas lambda$addHashSetThread$0()V
493
-com.Arthas lambda$deadThread$4(Ljava/lang/Object;Ljava/lang/Object;)V
494
-com.Arthas lambda$deadThread$5(Ljava/lang/Object;Ljava/lang/Object;)V
495
-com.Arthas lambda$cpuNormal$2()V
496
-Affect(row-cnt:16) cost in 6 ms.
497
-</code></pre>
498
-<h2 id="48-对变量的值很是好奇">4.8 对变量的值很是好奇</h2>
499
-<p>使用 <strong>ognl</strong> 命令,ognl 表达式可以轻松操作想要的信息。</p>
500
-<p>代码还是上面的示例代码,我们查看变量 <code>hashSet</code> 中的数据:</p>
501
-<p><img src="https://cdn.debug.group/git/2019/1571196786678.png" alt="" /></p>
502
-<p>查看静态变量 <code>hashSet</code> 信息。</p>
503
-<pre><code class="language-shell">[arthas@19856]$ ognl '@com.Arthas@hashSet'
504
-@HashSet[
505
- @String[count1],
506
- @String[count2],
507
- @String[count29],
508
- @String[count28],
509
- @String[count0],
510
- @String[count27],
511
- @String[count5],
512
- @String[count26],
513
- @String[count6],
514
- @String[count25],
515
- @String[count3],
516
- @String[count24],
517
-</code></pre>
518
-<p>查看静态变量 hashSet 大小。</p>
519
-<pre><code class="language-shell">[arthas@19856]$ ognl '@<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dfbcb0b2f19eadabb7beac9fb7beacb78cbaabf1acb6a5ba">[email&#160;protected]</a>()'
520
- @Integer[57]
521
-</code></pre>
522
-<p>甚至可以进行操作。</p>
523
-<pre><code class="language-shell">[arthas@19856]$ ognl '@<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="01626e6c2f40737569607241696072695264752f606565">[email&#160;protected]</a>(&quot;test&quot;)'
524
- @Boolean[true]
525
-[arthas@19856]$
526
-# 查看添加的字符
527
-[arthas@19856]$ ognl '@com.Arthas@hashSet' | grep test
528
- @String[test],
529
-[arthas@19856]$
530
-</code></pre>
531
-<p><code>ognl</code> 可以做很多事情,可以参考 <a href="https://github.com/alibaba/arthas/issues/71" target="_blank">ognl 表达式特殊用法( https://github.com/alibaba/arthas/issues/71 )</a>。</p>
532
-<h2 id="49-程序有没有问题">4.9 程序有没有问题</h2>
533
-<h3 id="491-运行较慢耗时较长">4.9.1 运行较慢、耗时较长</h3>
534
-<p>使用 <strong>trace</strong> 命令可以跟踪统计方法耗时</p>
535
-<p>这次换一个模拟代码。一个最基础的 Springboot 项目(当然,不想 Springboot 的话,你也可以直接在 UserController 里 main 方法启动)控制层 <code>getUser</code> 方法调用了 <code>userService.get(uid);</code>,这个方法中分别进行<code>check</code>、<code>service</code>、<code>redis</code>、<code>mysql</code>操作。</p>
536
-<pre><code class="language-java">@RestController
537
-@Slf4j
538
-public class UserController {
539
-
540
- @Autowired
541
- private UserServiceImpl userService;
542
-
543
- @GetMapping(value = &quot;/user&quot;)
544
- public HashMap&lt;String, Object&gt; getUser(Integer uid) throws Exception {
545
- // 模拟用户查询
546
- userService.get(uid);
547
- HashMap&lt;String, Object&gt; hashMap = new HashMap&lt;&gt;();
548
- hashMap.put(&quot;uid&quot;, uid);
549
- hashMap.put(&quot;name&quot;, &quot;name&quot; + uid);
550
- return hashMap;
551
- }
552
-}
553
-</code></pre>
554
-<p>模拟代码 Service:</p>
555
-<pre><code class="language-java">@Service
556
-@Slf4j
557
-public class UserServiceImpl {
558
-
559
- public void get(Integer uid) throws Exception {
560
- check(uid);
561
- service(uid);
562
- redis(uid);
563
- mysql(uid);
564
- }
565
-
566
- public void service(Integer uid) throws Exception {
567
- int count = 0;
568
- for (int i = 0; i &lt; 10; i++) {
569
- count += i;
570
- }
571
- log.info(&quot;service end {}&quot;, count);
572
- }
573
-
574
- public void redis(Integer uid) throws Exception {
575
- int count = 0;
576
- for (int i = 0; i &lt; 10000; i++) {
577
- count += i;
578
- }
579
- log.info(&quot;redis end {}&quot;, count);
580
- }
581
-
582
- public void mysql(Integer uid) throws Exception {
583
- long count = 0;
584
- for (int i = 0; i &lt; 10000000; i++) {
585
- count += i;
586
- }
587
- log.info(&quot;mysql end {}&quot;, count);
588
- }
589
-
590
- public boolean check(Integer uid) throws Exception {
591
- if (uid == null || uid &lt; 0) {
592
- log.error(&quot;uid不正确,uid:{}&quot;, uid);
593
- throw new Exception(&quot;uid不正确&quot;);
594
- }
595
- return true;
596
- }
597
-}
598
-
599
-</code></pre>
600
-<p>运行 Springboot 之后,使用 **trace== ** 命令开始检测耗时情况。</p>
601
-<pre><code class="language-shell">[arthas@6592]$ trace com.UserController getUser
602
-</code></pre>
603
-<p>访问接口 <code>/getUser</code> ,可以看到耗时信息,看到 <code>com.UserServiceImpl:get() </code>方法耗时较高。
604
-<img src="https://cdn.debug.group/git/2019/1571208153793.png" alt="" /></p>
605
-<p>继续跟踪耗时高的方法,然后再次访问。</p>
606
-<pre><code class="language-shell">[arthas@6592]$ trace com.UserServiceImpl get
607
-</code></pre>
608
-<p><img src="https://cdn.debug.group/git/2019/1571208245597.png" alt="" /></p>
609
-<p>很清楚的看到是 <code>com.UserServiceImpl </code>的 <code>mysql </code>方法耗时是最高的。</p>
610
-<pre><code class="language-java">Affect(class-cnt:1 , method-cnt:1) cost in 31 ms.
611
-`---ts=2019-10-16 14:40:10;thread_name=http-nio-8080-exec-8;id=1f;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@23a918c7
612
- `---[6.792201ms] com.UserServiceImpl:get()
613
- +---[0.008ms] com.UserServiceImpl:check() #17
614
- +---[0.076ms] com.UserServiceImpl:service() #18
615
- +---[0.1089ms] com.UserServiceImpl:redis() #19
616
- `---[6.528899ms] com.UserServiceImpl:mysql() #20
617
-</code></pre>
618
-<h3 id="492-统计方法耗时">4.9.2 统计方法耗时</h3>
619
-<p>使用 <strong>monitor</strong> 命令监控统计方法的执行情况。</p>
620
-<p>每5秒统计一次 <code>com.UserServiceImpl</code> 类的 <code>get</code> 方法执行情况。</p>
621
-<pre><code class="language-shell">monitor -c 5 com.UserServiceImpl get
622
-</code></pre>
623
-<p><img src="https://cdn.debug.group/git/2019/1571210158018.png" alt="" /></p>
624
-<h2 id="410-想观察方法信息">4.10 想观察方法信息</h2>
625
-<p>下面的示例用到了文章的前两个模拟代码。</p>
626
-<h3 id="4101-观察方法的入参出参信息">4.10.1 观察方法的入参出参信息</h3>
627
-<p>使用 <strong>watch</strong> 命令轻松查看输入输出参数以及异常等信息。</p>
628
-<pre><code class="language-shell"> USAGE:
629
- watch [-b] [-e] [-x &lt;value&gt;] [-f] [-h] [-n &lt;value&gt;] [-E] [-M &lt;value&gt;] [-s] class-pattern method-pattern express [condition-express]
630
-
631
- SUMMARY:
632
- Display the input/output parameter, return object, and thrown exception of specified method invocation
633
- The express may be one of the following expression (evaluated dynamically):
634
- target : the object
635
- clazz : the object's class
636
- method : the constructor or method
637
- params : the parameters array of method
638
- params[0..n] : the element of parameters array
639
- returnObj : the returned object of method
640
- throwExp : the throw exception of method
641
- isReturn : the method ended by return
642
- isThrow : the method ended by throwing exception
643
- #cost : the execution time in ms of method invocation
644
- Examples:
645
- watch -b org.apache.commons.lang.StringUtils isBlank params
646
- watch -f org.apache.commons.lang.StringUtils isBlank returnObj
647
- watch org.apache.commons.lang.StringUtils isBlank '{params, target, returnObj}' -x 2
648
- watch -bf *StringUtils isBlank params
649
- watch *StringUtils isBlank params[0]
650
- watch *StringUtils isBlank params[0] params[0].length==1
651
- watch *StringUtils isBlank params '#cost&gt;100'
652
- watch -E -b org\.apache\.commons\.lang\.StringUtils isBlank params[0]
653
-
654
- WIKI:
655
- https://alibaba.github.io/arthas/watch
656
-</code></pre>
657
-<p>常用操作:</p>
658
-<pre><code class="language-shell"># 查看入参和出参
659
-$ watch com.Arthas addHashSet '{params[0],returnObj}'
660
-# 查看入参和出参大小
661
-$ watch com.Arthas addHashSet '{params[0],returnObj.size}'
662
-# 查看入参和出参中是否包含 'count10'
663
-$ watch com.Arthas addHashSet '{params[0],returnObj.contains(&quot;count10&quot;)}'
664
-# 查看入参和出参,出参 toString
665
-$ watch com.Arthas addHashSet '{params[0],returnObj.toString()}'
666
-</code></pre>
667
-<p>查看入参出参。</p>
668
-<p><img src="https://cdn.debug.group/git/2019/1571196483469.png" alt="" /></p>
669
-<p>查看返回的异常信息。</p>
670
-<h3 id="4102-观察方法的调用路径">4.10.2 观察方法的调用路径</h3>
671
-<p>使用 <strong>stack</strong>命令查看方法的调用信息。</p>
672
-<pre><code class="language-shell"># 观察 类com.UserServiceImpl的 mysql 方法调用路径
673
-stack com.UserServiceImpl mysql
674
-</code></pre>
675
-<p><img src="https://cdn.debug.group/git/2019/1571210706602.png" alt="" /></p>
676
-<h3 id="4103-方法调用时空隧道">4.10.3 方法调用时空隧道</h3>
677
-<p>使用 <strong>tt</strong> 命令记录方法执行的详细情况。</p>
678
-<blockquote>
679
-<p><strong>tt</strong> 命令方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 。</p>
680
-</blockquote>
681
-<p>常用操作:</p>
682
-<p>开始记录方法调用信息:tt -t com.UserServiceImpl check</p>
683
-<p><img src="https://cdn.debug.group/git/2019/1571212007249.png" alt="" /></p>
684
-<p>可以看到记录中 INDEX=1001 的记录的 IS-EXP = true ,说明这次调用出现异常。</p>
685
-<p>查看记录的方法调用信息: tt -l</p>
686
-<p><img src="https://cdn.debug.group/git/2019/1571212080071.png" alt="" /></p>
687
-<p>查看调用记录的详细信息(-i 指定 INDEX): tt -i 1001</p>
688
-<p><img src="https://cdn.debug.group/git/2019/1571212151064.png" alt="" /></p>
689
-<p>可以看到 INDEX=1001 的记录的异常信息。</p>
690
-<p>重新发起调用,使用指定记录,使用 -p 重新调用。</p>
691
-<pre><code class="language-java">tt -i 1001 -p
692
-</code></pre>
693
-<p><img src="https://cdn.debug.group/git/2019/1571212227058.png" alt="" /></p>
694
-<h2 id="45-火焰图分析">4.5. 火焰图分析</h2>
695
-<p>最近 Arthas 性能分析工具上线了<strong>火焰图</strong>分析功能,Arthas 使用 <strong>async-profiler</strong> 生成 CPU/内存火焰图进行性能分析,弥补了之前内存分析的不足。在 Arthas 上使用还是比较方便的。</p>
696
-<p><strong><code>profiler</code></strong> 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。</p>
697
-<p><strong><code>profiler</code></strong> 命令基本运行结构是 <strong><code>profiler action [actionArg]</code></strong></p>
698
-<h3 id="451使用案例"><strong>4.5.1.使用案例</strong></h3>
699
-<p><strong>开启 prifilter</strong></p>
700
-<p>默认情况下,生成的是cpu的火焰图,即event为<code>cpu</code>。可以用<code>--event</code>参数来指定,使用 start 命令开始捕获信息。</p>
701
-<pre><code class="language-shell">$ profiler start
702
-Started [cpu] profiling
703
-</code></pre>
704
-<p>获取已采集的sample的数量</p>
705
-<pre><code class="language-shell">$ profiler getSamples
706
-23
707
-</code></pre>
708
-<p>查看 profiler状态,可以查看当前 profiler 在采样哪种 <code>event </code>和进行的采样时间。</p>
709
-<pre><code class="language-shell">$ profiler status
710
-
711
-[cpu] profiling is running for 4 seconds
712
-</code></pre>
713
-<p><strong>停止profiler</strong></p>
714
-<p>生成svg格式火焰图</p>
715
-<pre><code class="language-shell">$ profiler stop
716
-profiler output file: /tmp/demo/arthas-output/20191125-135546.svg
717
-OK
718
-</code></pre>
719
-<p>默认情况下,生成的结果保存到应用的<code>工作目录</code>下的<code>arthas-output</code>目录。可以通过 <code>--file</code>参数来指定输出结果路径。</p>
720
-<p>比如:</p>
721
-<pre><code class="language-shell">$ profiler stop --file /tmp/output.svg
722
-</code></pre>
723
-<p><strong>HTML 格式输出</strong></p>
724
-<p>默认情况下,结果文件是<code>svg</code>格式,如果想生成<code>html</code>格式,可以用<code>--format</code>参数指定:$ profiler stop --format html</p>
725
-<p><strong>查看 profilter</strong></p>
726
-<p>默认情况下,arthas使用3658端口,则可以打开: http://localhost:3658/arthas-output/ 查看到<code>arthas-output</code>目录下面的profiler结果:</p>
727
-<p><img src="https://cdn.debug.group/img/23/10/151226645.webp" alt="" /></p>
728
-<p>点击可以查看具体的结果:<strong>火焰图里,横条越长,代表使用的越多,从下到上是调用堆栈信息</strong></p>
729
-<p><img src="https://cdn.debug.group/img/23/10/151303153.webp" alt="" /></p>
730
-<p>**profilter 自持多种分析方式,**常见的有 event: cpu|alloc|lock|cache-misses etc. 比如要分析内存使用情况。</p>
731
-<p>$ profiler start --event alloc</p>
732
-<h3 id="452-复杂命令">4.5.2. 复杂命令</h3>
733
-<p>比如开始采样:</p>
734
-<pre><code class="language-shell">profiler execute 'start'
735
-</code></pre>
736
-<p>停止采样,并保存到指定文件里:</p>
737
-<pre><code class="language-shell">profiler execute 'stop,file=/tmp/result.svg'
738
-</code></pre>
739
-<p>文中代码已经上传到 <a href="https://github.com/niumoo/lab-notes/" target="_blank">Github</a>。</p>
740
-
741
- </div>
742
-</div>
743
-
744
-</html>
... ...
\ No newline at end of file