代码示例如下
package cn.itxs;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import java.util.UUID;import java.util.concurrent.TimeUnit;@SpringBootApplicationpublic class App { public static void main(String[] args) { SpringApplication.run(App.class,args); new Thread( () -> { while (true) { String str = UUID.randomUUID().toString().replaceAll("-", ""); } },"cpu demo thread").start(); new Thread( () -> { while (true) { String str = UUID.randomUUID().toString().replaceAll("-", ""); try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } },"cpu with sleep thread").start(); }}
安装与利用推举办法
# 下载`arthas-boot.jar`这种也是官方推举的办法curl -O https://arthas.aliyun.com/arthas-boot.jar# 启动arthas-boot.jar,必须启动至少一个 java程序,否则会自动退出。运行此命令会自动创造 java进程,输入须要 attach 进程对应的序列号,例如,输入1按回车则会监听该进程。java -jar arthas-boot.jar# 比如输入JVM (jvm实时运行状态,内存利用情形等)
package cn.itxs.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RestController@RequestMapping("/thread")public class ThreadController { private Object obj1 = new Object(); private Object obj2 = new Object(); @RequestMapping("/test") @ResponseBody public String test(){ new Thread(() -> { synchronized (obj1){ try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { synchronized (obj2){ System.out.printf("thread 1实行到此"); } } } },"thread 1").start(); new Thread(() -> { synchronized (obj2) { synchronized (obj1){ System.out.printf("thread 2实行到此"); } } },"thread 2").start(); return "thread test"; }}
SpringBoot启动类

package cn.itxs;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class App { public static void main(String[] args) { SpringApplication.run(App.class,args); }}
# 访问页面http://192.168.50.100:8080/cpu/test# 仪表盘命令,通过上面我们可以创造线程ID为29也即是线程名称为“cpu demo thread”占用的cpu较高dashboard
# 当前最忙的前N个线程 thread -b, ##找出当前壅塞其他线程的线程 thread -n 5 -i 1000 #间隔一定韶光后展示,本例中可以看到最忙CPU线程为id=45,代码行数为19thread -n 5 # jad查看反编译的代码jad cn.itxs.controller.CpuController
线程去世锁示例
package cn.itxs.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RestController@RequestMapping("/thread")public class ThreadController { private Object obj1 = new Object(); private Object obj2 = new Object(); @RequestMapping("/test") @ResponseBody public String test(){ new Thread(() -> { synchronized (obj1){ try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { } synchronized (obj2){ System.out.println("thread 1实行到此"); } } },"thread 1").start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { synchronized (obj2) { synchronized (obj1){ System.out.println("thread 2实行到此"); } } },"thread 2").start(); return "thread test"; }}
# 启动SpringBoot演示程序,访问页面http://192.168.50.100:8080/thread/test# 运行arthas,查看线程thread
# 查看壅塞线程thread -b# jad反编译查看代码jad --source-only cn.itxs.controller.ThreadController
准备一个有问题的java类
package cn.itxs.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.UUID;import java.util.concurrent.TimeUnit;@RestController@RequestMapping("/hot")public class HotController { @RequestMapping("/test") @ResponseBody public String test(){ boolean flag = true; if (flag) { System.out.println("开始处理逻辑"); throw new RuntimeException("出非常了"); } System.out.println("结束流程"); return "hot test"; }}
启动Spring Boot程序,访问页面http://192.168.50.94:8080/hot/test创造报错,当我们剖析到这段程序出非常后,我们剖析非常后进行线上代码修正一直机不重新发包的情形下的热更新,操作流程如下:
# 第一步:`jad命令` 将须要变动的文件前辈行反编译,保存下来 ,编译器修正,-c 指定这个类的classloader的哈希值,–source-only只显示源码,末了是文件反编译之后的存放路径jad --source-only cn.itxs.controller.HotController > /home/commons/arthas/data/HotController.java# 我们将HotController.java中的throw new RuntimeException("出非常了")代码删掉,修正完后须要将类重新加载到JVM# 第二步:`SC命令` 查找当前类是哪个classLoader加载的,首先,利用sc命令找到要修正的类.sc全称-search class, -d表示detail,紧张是为了获取classLoader的hash值sc -d HotController | grep classLoaderclassLoaderHash 6267c3bb #类加载器 编号 # 第三步:`MC命令` 用指定的classloader重新将类在内存中编译mc -c 6267c3bb /home/commons/arthas/data/HotController.java -d /home/commons/arthas/class# 第四步:`redefine命令` 将编译后的类加载到JVM,参数是编译后的.class文件地址redefine /home/commons/arthas/class/cn/itxs/controller/HotController.class
以上操作后再次访问一下页面http://192.168.50.94:8080/hot/test,创造非常没有了程序已经是我们修正精确后的,class文件更换成功,功能确实很强大。
上面我们是手工一步步实行,当然我们可以利用shell脚本串起来大略操作。
此外还可以安装Alibaba Cloud Toolkit热支配组件(一键retransform),热支配组件支持一键将编辑器中修正的 Java 源码快速编译,并更新到远端运用做事中,免去手动 dump、mc 的过程。此外,也可以一键还原 retransform 的类文件。
由于Arthas命令还是较繁芜,Arthas-idea插件(部分命令可视化)是一个帮助天生命令的IDEA插件,利用文档:https://www.yuque.com/arthas-idea-plugin;
安装基于Arthas实现的大略好用的热支配插件ArthasHotSwap可以一键天生热支配命令,提高我们线上掩护的效率。
线上问题常见定位watch(方法实行数据不雅观测)
package cn.itxs.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;import java.util.Random;@RestController@RequestMapping("/watch")public class WatchController { private static Random random = new Random(); private int illegalArgumentCount = 0; @RequestMapping("/test") @ResponseBody public String test(){ String res = null; try { int number = random.nextInt() / 10000; List<Integer> idStrs = this.getIdStr(number); res = printList(number, idStrs); } catch (Exception e) { System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage()); } return res; } private List<Integer> getIdStr(int number) { if (number < 5) { ++this.illegalArgumentCount; throw new IllegalArgumentException("number is: " + number + ", need >= 5"); } ArrayList<Integer> result = new ArrayList<Integer>(); int count = 2; while (count <= number) { if (number % count == 0) { result.add(count); number /= count; count = 2; continue; } ++count; } return result; } private String printList(int number, List<Integer> primeFactors) { StringBuffer sb = new StringBuffer(number + "="); for (int factor : primeFactors) { sb.append(factor).append(''); } if (sb.charAt(sb.length() - 1) == '') { sb.deleteCharAt(sb.length() - 1); } System.out.println(sb); return sb.toString(); }}
启动Spring Boot程序,通过Jmeter每秒访问一次http://192.168.50.100:8080/watch/test
# Arthas中的watch命令可以让我们能方便的不雅观察到指定方法的调用情形,可以不雅观察到返回值,入参,以及变量等。# watch 全路径类名 方法名 不雅观察表达式 -x 3 ,不雅观察表达式匹配ognl表达式,不雅观察的维度也比较多。# 比如:watch cn.itxs.controller.WatchController printList "{params,returnObj}" -x 3# 查看printList方法的入参和出参,-x表示的是遍历结果深度默认1,只会打印工具的堆地址,看不到详细的属性值,-x 2会打印结果的属性值的信息 -x 3会输出入参属性值和结果属性值# -n 1只抓紧一次,由于我们这里是仿照一贯要求的watch cn.itxs.controller.WatchController printList '{params}' -n 1# -x 表示的是遍历结果深度默认3watch cn.itxs.controller.WatchController printList '{params}' -n 1 -x 3# params[0]代表第一个参数watch cn.itxs.controller.WatchController printList '{params[0]}' -n 1 -x 3
# 方法的返回值watch cn.itxs.controller.WatchController getIdStr '{returnObj}' -n 1 -x 3# 方法参数和返回值watch cn.itxs.controller.WatchController getIdStr '{params,returnObj}' -n 1 -x 3
# 不雅观察方法实行前后当前工具属性值watch cn.itxs.controller.WatchController getIdStr 'target.illegalArgumentCount'
# 不雅观察非常信息,不雅观察表达式里增加throwExp就好了。如果增加-e 参数就能过滤掉非非常的监听了。
# 在不雅观察表达式后面,我们可以增加条件表达式,例如按韶光过滤:#cost>0.5,单位是毫秒,那么掌握台输出来的都是耗时在0.5毫秒以上的方法调用watch cn.itxs.controller.WatchController getIdStr '{params}' '#cost>0.5'
# 按条件过滤不雅观察params[1].size>4:这里支持ognl表达式。下面例子的意思是:第二个参数(也便是List primeFactors),的size大于4的时候才不雅观察入参。watch cn.itxs.controller.WatchController printList '{params}' 'params[1].size>4' -x 3
monitor结果包括如下
timestamp:韶光戳 class:Java类method:方法(布局方法、普通方法)total:调用次数success:成功次数fail:失落败次数rt:均匀RTfail-rate:失落败率# -c :统计周期,默认值为10秒monitor -c 10 cn.itxs.controller.WatchController getIdStr# 在方法调用之前打算condition-express,方法后可带表达式monitor -b -c 10 cn.itxs.controller.WatchController getIdStr
trace
# trace:方法内部调用路径,并输出方法路径上的每个节点上耗时trace cn.itxs.controller.WatchController test -n 2#包含jdk的函数--skipJDKMethod <value> skip jdk method trace, default value true.默认情形下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数,须要显式设置--skipJDKMethod false。trace --skipJDKMethod false cn.itxs.controller.WatchController test -n 2# 调用耗时过滤,只会展示耗时大于10ms的调用路径,有助于在排查问题的时候,只关注非常情形trace cn.itxs.controller.WatchController test '#cost > 1'
# 输出当前方法被调用的调用路径,getIdStr是从test方法调用进来的stack cn.itxs.controller.WatchController getIdStr -n 1# 输出当前方法被调用的调用路径,条件表达过滤,第0个参数小于0,也可以根据实行韶光来过滤,'#cost>1'stack cn.itxs.controller.WatchController getIdStr 'params[0]<0' -n 1
tt
tt(TimeTunnel):方法实行数据的时旷地道,记录下指定方法每次调用的入参和返复书息,并能对这些不同的韶光下调用进行不雅观测。对付一个最基本的利用来说,便是记录下当前方法的每次调用环境现场。
# 记录指定方法的每次调用环境现场tt -t cn.itxs.controller.WatchController getIdStr # 列出所有调用记录tt -l cn.itxs.controller.WatchController getIdStr
# 筛选调用记录tt -s 'method.name=="getIdStr"'# 查看调用信息tt -i 1001
# 重新发起一次调用tt -i 1001 -p
# 启动时指定Linux的ipjava -jar arthas-boot.jar --target-ip 192.168.50.94
profiler 命令支持天生运用热点的火焰图。实质上是通过不断的采样,然后把网络到的采样结果天生火焰图。一样平常剖析性能可以先通过Arthas profiler命令天生jfr文件;在本地通过 jprofiler来剖析 jfr文件,定位谁在调用我。
# 启动profiler 默认情形下,天生的是cpu的火焰图,即event为cpu。可以用--event参数来指定profiler start# 获取已采集的sample的数量profiler getSamples# 查看profiler状态profiler status# 停滞profiler 天生html格式结果,默认情形下,结果文件是html格式,也可以用--format参数指定;或者在--file参数里用文件名指名格式。比如--file /tmp/result.htmlprofiler stop --format html
通过浏览器查看arthas-output下面的profiler结果,http://192.168.50.100:3658/arthas-output/
# profiler支持的eventsprofiler list
# 可以用--event参数指定要采样的事宜,比如对alloc事宜进入采样:profiler start --event alloc# 利用execute来实行繁芜的命令profiler execute 'start,framebuf=5000000'# 天生 jfr格式结果;把稳,jfr只支持在 start时配置。如果是在stop时指定,则不会生效。profiler start --file /tmp/test.jfr# 配置 include/exclude 来过滤数据profiler start --include 'java/' --include 'demo/' --exclude 'Unsafe.park'# profiler实行 300 秒自动结束,可以用 -d/--duration 参数指定profiler start --duration 300
{ "action": "exec", "requestId": "req112", "sessionId": "94766d3c-8b39-42d3-8596-98aee3ccbefb", "consumerId": "955dbd1325334a84972b0f3ac19de4f7_2", "command": "version", "execTimeout": "10000"}
docker利用,很多时候,运用在docker里涌现arthas无法事情的问题,是由于运用没有安装 JDK ,而是安装了 JRE 。如果只安装了 JRE,则会短缺很多JAVA的命令行工具和类库,Arthas也没办法正常事情,可以利用公开的JDK镜像和包管理软件来安装这两种办法在Docker里利用JDK。# 选择须要监控运用的进程编号,回车后Arthas会attach到目标进程上,并输出日志: docker exec -it arthas-demo /bin/sh -c "java -jar /opt/arthas/arthas-boot.jar" # 乃至我们可以直接把arthas放到容器镜像文件中:Arthas Spring Boot Starter:运用启动后,spring会启动arthas,并且attach自身进程。 <dependency> <groupId>com.taobao.arthas</groupId> <artifactId>arthas-spring-boot-starter</artifactId> <version>${arthas.version}</version> </dependency>非spring boot运用利用办法
<dependency> <groupId>com.taobao.arthas</groupId> <artifactId>arthas-agent-attach</artifactId> <version>${arthas.version}</version> </dependency> <dependency> <groupId>com.taobao.arthas</groupId> <artifactId>arthas-packaging</artifactId> <version>${arthas.version}</version> </dependency>
import com.taobao.arthas.agent.attach.ArthasAgent; public class ArthasAttachExample { public static void main(String[] args) { ArthasAgent.attach(); }}
原文来自IT小神博客 http://www.itxiaoshen.cn