赞
踩
关注可以查看更多粉丝专享blog~
其实很早之前就听说过Arthas这个工具,只知道是在线诊断工具,也一直没有去了解,该怎么用?有啥用?听说好像很厉害?也一直停留在听说阶段,不知道大家有没有同感。但是在去年(2019)下半年的时候需要处理的生产环境问题越来越多,也越来越复杂,定位问题变得越来越繁琐,总结起来遇到最多的问题就是以下几点:
当我看到官网简介的时候,嗯?!这不就是专门为我准备的吗?我决定玩儿一下,盘它!
Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
Arthas支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
Arthas的使用方式非常简单
直接到github releases中下载自己想要的版本,arthas-bin.zip
解压后可以看到有很多jar包,我们需要使用的就是arthas-boot.jar,因为我之前用过其他版本所以我把它修改一下名字arthas-boot-3.3.6.jar
大家可以看到压缩包中是有arthas-demo.jar的,但是demo里面的场景比较少,所以我这边自己模拟我们经常遇到的场景,也方便我们修改和调试。我这边就拿我前两天做Spring Boot 使用docker整合MongoDB的demo来用。模拟了最常见的一些场景,还原上面的几个点。我这边直接添加了一个Controller模拟了常规方法和死锁,然后用Arthas来排查问题,首先需要启动项目。
package com.example.mongo.controller; import com.example.mongo.entity.UserEntity; import com.example.mongo.service.UserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Resource; import lombok.SneakyThrows; @RestController @RequestMapping("arthas") public class ArthasController { private static ExecutorService executors = Executors.newFixedThreadPool(2); private static String NAME = "DW"; @Resource private UserService userService; @SneakyThrows @GetMapping("normal") public List<String> normal( @RequestParam("param") Integer param ) { if (Objects.isNull(param)) throw new IllegalArgumentException("param is null"); userService.findById(1L); return Arrays.asList("david", "david1"); } @GetMapping("deadlock") public void deadlock() { UserEntity a = new UserEntity(); UserEntity b = new UserEntity(); executors.execute(() -> { synchronized (a) { System.out.println(Thread.currentThread().getName() + "get A lock! start sleep"); NAME = Thread.currentThread().getName() + "1a"; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "sleep end start get B lock!"); synchronized (b) { NAME = Thread.currentThread().getName() + "1b"; System.out.println(Thread.currentThread().getName() + "get B lock!"); } } }); executors.execute(() -> { synchronized (b) { System.out.println(Thread.currentThread().getName() + "get B lock! start sleep"); NAME = Thread.currentThread().getName() + "2b"; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "sleep end start get A lock!"); synchronized (a) { System.out.println(Thread.currentThread().getName() + "get A lock!"); NAME = Thread.currentThread().getName() + "2a"; } } }); } } // userService#findById @Override @SneakyThrows public UserEntity findById( Long id ) { List<UserEntity> david = this.findByName("david"); if (!CollectionUtils.isEmpty(david)) { david.forEach(System.out::println); } Thread.sleep(100); return userRepository.findById(id).orElse(new UserEntity()); }
[INFO] arthas-client connect 127.0.0.1 3658
因为我们是本地调试所以可以直接使用浏览器访问localhost:3658
,在web console中来操作,效果如下。【Arthas官网】
默认情况下,arthas只listen 127.0.0.1,所以如果想从远程连接,则可以使用 --target-ip参数指定listen的IP,更多参考-h的帮助说明。 注意会有安全风险,考虑下面的tunnel server的方案。
这种场景下我们可以使用watch命令监控我们的目标方法,查看入参,出参和异常信息。
命令格式:watch <类的全限定名> <方法名> '{params, returnObj, throwExp}'
params如果是数组的话可以使用params[0],params[1]
来查看,returnObj
表示返回值,throwExp
表示异常信息,如果只想看入参,只写params信息就可以,returnObj
和throwExp
也是一样。输入watch com.example.mongo.controller.ArthasController normal '{params, returnObj, throwExp}'
回车即开始监控。
不传参试一下http://localhost:8080/arthas/normal?param
,报错了,param is null
。
传参http://localhost:8080/arthas/normal?param=1
,入参出参都打印了,但是没有具体值,修改一下命令,watch com.example.mongo.controller.ArthasController normal '{params[0], returnObj[0], returnObj[1], throwExp}'
重新执行
修改命令之后效果如下:
两种办法:
既然主要是讲Arthas那肯定是选择Arthas啦,下次一定!
这里需要使用到jad
命令,直接对我们的目标类或者目标方法进行反编译查看。
命令格式:jad <类的全限定名>/<类的全限定名> <方法名>
jad com.example.mongo.controller.ArthasController normal
回车,即可查看正在运行的代码到底是什么样子,是美发不上去呢,还是写bug,拒绝甩锅。由于类信息较长,就不截图了输入jad com.example.mongo.controller.ArthasController
回车即可查看。命令格式:trace <类的全限定名> <方法名>
trace com.example.mongo.controller.ArthasController normal
回车,可以看到时间主要消耗在UserService#findById()
trace com.example.mongo.service.UserService findById
,可以看到总耗时为109ms,方法耗时综合在9ms左右,Thread.sleep(100)不会打印,这个时候对比一下代码就可以锁定具体问题了。命令格式:stack <类的全限定名> <方法名>
stack com.example.mongo.controller.ArthasController normal
命令格式:getstatic <类的全限定名> <方法名>
getstatic com.example.mongo.controller.ArthasController NAME
回车。http://localhost:8080/arthas/deadlock
复现死锁,现在两个线程相互等待对方释放资源。Thread
命令来查看我们的线程信息。可以看到pool-1-thread-1
和pool-1-thread-2
阻塞了,这也是为什么建议大家在使用线程池的时候一定要命名的原因,前面线程池的blog有详细讲解。传送门Davids原理探究:ThreadPoolExecutor原理。thread -b
直接查看block状态的线程信息,锁定原因。thread -n <num>
查看cpu使用率最高的num个线程用于排查cpu占用高的场景,相关信息也非常详细。一般在配置JVM的时候建议打开dump日志,一旦出现内存溢出方便排查,但是在内存达到报警阈值或者内存在某个版本发布之后长期处于高占用状态的时候,我们可以采用Arthas进行heapdump分析原因(类似jmap命令的heap dump功能)。
命令格式:
heapdump <path> 示例:heapdump /tmp/dump.hprof
heapdump --live <path> 示例:heapdump --live /tmp/dump.hprof
heapdump 示例:heapdump
其实Arthas还有非常多的丰富的功能,本篇博客只是介绍了一些非常基础和我常用的,而且这些命令还有很多高级用法,大家感兴趣的可以去玩一玩。官网传送门
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。