e34d0d20afdb84e1bfe7cab0dfd73817e40559d6
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 <pid> |
|
| 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' <pid> |
|
| 191 | + java -jar arthas-boot.jar -f batch.as <pid> |
|
| 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 | + * <p> |
|
| 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(() -> { |
|
| 355 | + int count = 0; |
|
| 356 | + while (true) { |
|
| 357 | + try { |
|
| 358 | + hashSet.add("count" + 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(() -> { |
|
| 378 | + while (true) { |
|
| 379 | + log.info("cpu start 100"); |
|
| 380 | + } |
|
| 381 | + }); |
|
| 382 | + // 添加到线程 |
|
| 383 | + executorService.submit(thread); |
|
| 384 | + } |
|
| 385 | + |
|
| 386 | + /** |
|
| 387 | + * 普通消耗CPU的线程 |
|
| 388 | + */ |
|
| 389 | + private static void cpuNormal() { |
|
| 390 | + for (int i = 0; i < 10; i++) { |
|
| 391 | + new Thread(() -> { |
|
| 392 | + while (true) { |
|
| 393 | + log.info("cpu start"); |
|
| 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(() -> { |
|
| 409 | + while (true) { |
|
| 410 | + log.debug("thread start"); |
|
| 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(() -> { |
|
| 431 | + synchronized (resourceA) { |
|
| 432 | + log.info(Thread.currentThread() + " get ResourceA"); |
|
| 433 | + try { |
|
| 434 | + Thread.sleep(1000); |
|
| 435 | + } catch (InterruptedException e) { |
|
| 436 | + e.printStackTrace(); |
|
| 437 | + } |
|
| 438 | + log.info(Thread.currentThread() + "waiting get resourceB"); |
|
| 439 | + synchronized (resourceB) { |
|
| 440 | + log.info(Thread.currentThread() + " get resourceB"); |
|
| 441 | + } |
|
| 442 | + } |
|
| 443 | + }); |
|
| 444 | + |
|
| 445 | + Thread threadB = new Thread(() -> { |
|
| 446 | + synchronized (resourceB) { |
|
| 447 | + log.info(Thread.currentThread() + " get ResourceB"); |
|
| 448 | + try { |
|
| 449 | + Thread.sleep(1000); |
|
| 450 | + } catch (InterruptedException e) { |
|
| 451 | + e.printStackTrace(); |
|
| 452 | + } |
|
| 453 | + log.info(Thread.currentThread() + "waiting get resourceA"); |
|
| 454 | + synchronized (resourceA) { |
|
| 455 | + log.info(Thread.currentThread() + " get resourceA"); |
|
| 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(() -> { |
|
| 534 | + synchronized (resourceA) { |
|
| 535 | + log.info(Thread.currentThread() + " get ResourceA"); |
|
| 536 | + try { |
|
| 537 | + Thread.sleep(1000); |
|
| 538 | + } catch (InterruptedException e) { |
|
| 539 | + e.printStackTrace(); |
|
| 540 | + } |
|
| 541 | + log.info(Thread.currentThread() + "waiting get resourceB"); |
|
| 542 | + synchronized (resourceB) { |
|
| 543 | + log.info(Thread.currentThread() + " get resourceB"); |
|
| 544 | + } |
|
| 545 | + } |
|
| 546 | + }); |
|
| 547 | + |
|
| 548 | + Thread threadB = new Thread(() -> { |
|
| 549 | + synchronized (resourceB) { |
|
| 550 | + log.info(Thread.currentThread() + " get ResourceB"); |
|
| 551 | + try { |
|
| 552 | + Thread.sleep(1000); |
|
| 553 | + } catch (InterruptedException e) { |
|
| 554 | + e.printStackTrace(); |
|
| 555 | + } |
|
| 556 | + log.info(Thread.currentThread() + "waiting get resourceA"); |
|
| 557 | + synchronized (resourceA) { |
|
| 558 | + log.info(Thread.currentThread() + " get resourceA"); |
|
| 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 <init>()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 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 protected]</a>("test")' |
|
| 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 = "/user") |
|
| 709 | + public HashMap<String, Object> getUser(Integer uid) throws Exception { |
|
| 710 | + // 模拟用户查询 |
|
| 711 | + userService.get(uid); |
|
| 712 | + HashMap<String, Object> hashMap = new HashMap<>(); |
|
| 713 | + hashMap.put("uid", uid); |
|
| 714 | + hashMap.put("name", "name" + 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 < 10; i++) { |
|
| 736 | + count += i; |
|
| 737 | + } |
|
| 738 | + log.info("service end {}", count); |
|
| 739 | + } |
|
| 740 | + |
|
| 741 | + public void redis(Integer uid) throws Exception { |
|
| 742 | + int count = 0; |
|
| 743 | + for (int i = 0; i < 10000; i++) { |
|
| 744 | + count += i; |
|
| 745 | + } |
|
| 746 | + log.info("redis end {}", count); |
|
| 747 | + } |
|
| 748 | + |
|
| 749 | + public void mysql(Integer uid) throws Exception { |
|
| 750 | + long count = 0; |
|
| 751 | + for (int i = 0; i < 10000000; i++) { |
|
| 752 | + count += i; |
|
| 753 | + } |
|
| 754 | + log.info("mysql end {}", count); |
|
| 755 | + } |
|
| 756 | + |
|
| 757 | + public boolean check(Integer uid) throws Exception { |
|
| 758 | + if (uid == null || uid < 0) { |
|
| 759 | + log.error("uid不正确,uid:{}", uid); |
|
| 760 | + throw new Exception("uid不正确"); |
|
| 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 <value>] [-f] [-h] [-n <value>] [-E] [-M <value>] [-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>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("count10")}' |
|
| 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 <pid> |
|
| 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' <pid> |
|
| 112 | - java -jar arthas-boot.jar -f batch.as <pid> |
|
| 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 | - * <p> |
|
| 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(() -> { |
|
| 231 | - int count = 0; |
|
| 232 | - while (true) { |
|
| 233 | - try { |
|
| 234 | - hashSet.add("count" + 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(() -> { |
|
| 254 | - while (true) { |
|
| 255 | - log.info("cpu start 100"); |
|
| 256 | - } |
|
| 257 | - }); |
|
| 258 | - // 添加到线程 |
|
| 259 | - executorService.submit(thread); |
|
| 260 | - } |
|
| 261 | - |
|
| 262 | - /** |
|
| 263 | - * 普通消耗CPU的线程 |
|
| 264 | - */ |
|
| 265 | - private static void cpuNormal() { |
|
| 266 | - for (int i = 0; i < 10; i++) { |
|
| 267 | - new Thread(() -> { |
|
| 268 | - while (true) { |
|
| 269 | - log.info("cpu start"); |
|
| 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(() -> { |
|
| 285 | - while (true) { |
|
| 286 | - log.debug("thread start"); |
|
| 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(() -> { |
|
| 307 | - synchronized (resourceA) { |
|
| 308 | - log.info(Thread.currentThread() + " get ResourceA"); |
|
| 309 | - try { |
|
| 310 | - Thread.sleep(1000); |
|
| 311 | - } catch (InterruptedException e) { |
|
| 312 | - e.printStackTrace(); |
|
| 313 | - } |
|
| 314 | - log.info(Thread.currentThread() + "waiting get resourceB"); |
|
| 315 | - synchronized (resourceB) { |
|
| 316 | - log.info(Thread.currentThread() + " get resourceB"); |
|
| 317 | - } |
|
| 318 | - } |
|
| 319 | - }); |
|
| 320 | - |
|
| 321 | - Thread threadB = new Thread(() -> { |
|
| 322 | - synchronized (resourceB) { |
|
| 323 | - log.info(Thread.currentThread() + " get ResourceB"); |
|
| 324 | - try { |
|
| 325 | - Thread.sleep(1000); |
|
| 326 | - } catch (InterruptedException e) { |
|
| 327 | - e.printStackTrace(); |
|
| 328 | - } |
|
| 329 | - log.info(Thread.currentThread() + "waiting get resourceA"); |
|
| 330 | - synchronized (resourceA) { |
|
| 331 | - log.info(Thread.currentThread() + " get resourceA"); |
|
| 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(() -> { |
|
| 391 | - synchronized (resourceA) { |
|
| 392 | - log.info(Thread.currentThread() + " get ResourceA"); |
|
| 393 | - try { |
|
| 394 | - Thread.sleep(1000); |
|
| 395 | - } catch (InterruptedException e) { |
|
| 396 | - e.printStackTrace(); |
|
| 397 | - } |
|
| 398 | - log.info(Thread.currentThread() + "waiting get resourceB"); |
|
| 399 | - synchronized (resourceB) { |
|
| 400 | - log.info(Thread.currentThread() + " get resourceB"); |
|
| 401 | - } |
|
| 402 | - } |
|
| 403 | - }); |
|
| 404 | - |
|
| 405 | - Thread threadB = new Thread(() -> { |
|
| 406 | - synchronized (resourceB) { |
|
| 407 | - log.info(Thread.currentThread() + " get ResourceB"); |
|
| 408 | - try { |
|
| 409 | - Thread.sleep(1000); |
|
| 410 | - } catch (InterruptedException e) { |
|
| 411 | - e.printStackTrace(); |
|
| 412 | - } |
|
| 413 | - log.info(Thread.currentThread() + "waiting get resourceA"); |
|
| 414 | - synchronized (resourceA) { |
|
| 415 | - log.info(Thread.currentThread() + " get resourceA"); |
|
| 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 <init>()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 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 protected]</a>("test")' |
|
| 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 = "/user") |
|
| 544 | - public HashMap<String, Object> getUser(Integer uid) throws Exception { |
|
| 545 | - // 模拟用户查询 |
|
| 546 | - userService.get(uid); |
|
| 547 | - HashMap<String, Object> hashMap = new HashMap<>(); |
|
| 548 | - hashMap.put("uid", uid); |
|
| 549 | - hashMap.put("name", "name" + 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 < 10; i++) { |
|
| 569 | - count += i; |
|
| 570 | - } |
|
| 571 | - log.info("service end {}", count); |
|
| 572 | - } |
|
| 573 | - |
|
| 574 | - public void redis(Integer uid) throws Exception { |
|
| 575 | - int count = 0; |
|
| 576 | - for (int i = 0; i < 10000; i++) { |
|
| 577 | - count += i; |
|
| 578 | - } |
|
| 579 | - log.info("redis end {}", count); |
|
| 580 | - } |
|
| 581 | - |
|
| 582 | - public void mysql(Integer uid) throws Exception { |
|
| 583 | - long count = 0; |
|
| 584 | - for (int i = 0; i < 10000000; i++) { |
|
| 585 | - count += i; |
|
| 586 | - } |
|
| 587 | - log.info("mysql end {}", count); |
|
| 588 | - } |
|
| 589 | - |
|
| 590 | - public boolean check(Integer uid) throws Exception { |
|
| 591 | - if (uid == null || uid < 0) { |
|
| 592 | - log.error("uid不正确,uid:{}", uid); |
|
| 593 | - throw new Exception("uid不正确"); |
|
| 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 <value>] [-f] [-h] [-n <value>] [-E] [-M <value>] [-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>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("count10")}' |
|
| 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 |