Arthas 实战:从入门到线上排障,使用火焰图定位 Java 性能热点

Arthas 实战:从入门到线上排障,使用火焰图定位 Java 性能热点 1. Arthas 是什么Arthas 是阿里开源的 Java 诊断工具。它以 Java Agent 的方式附着到目标 JVM在不修改业务代码、不重启服务的前提下帮助开发者查看线程、内存、类加载、方法调用、入参、返回值、异常和执行耗时。你是不是也有遇到过这些问题某个接口突然变慢慢在哪个方法是不是死锁了某个方法的入参到底是什么线上运行的代码和本地代码是否一致某个异常是谁抛出的CPU 为什么突然飙高是什么原因造成的呢我写的代码没有执行是部署分支不对还是我压根没提交呢那arthas刚好就是解决这些问题的简单来说日志告诉你“发生过什么”Arthas 让你看到“此刻正在发生什么”。2. Arthas 的工作原理Arthas 会通过 Java 的 Attach API 连接到指定JVM并利用 Instrumentation 对目标类进行增强。因此它能观察方法执行过程但也意味着线上使用必须谨慎范围要小、次数要有限制、观察结束后要退出或恢复增强。3.安装与启动在上面我们了解了它是什么以及怎么工作的现在我们就来安装启动实操一下。安装curl -O https://arthas.aliyun.com/arthas-boot.jar启动java -jar arthas-boot.jar在自己需要安装的地方打开终端命令窗口执行安装指令注意然后启动的时候要有一段jvm进程是在运行的我这边为了简单做个demo已经把代码粘贴到这边了import java.lang.management.ManagementFactory; import java.time.LocalTime; import java.util.concurrent.ThreadLocalRandom; /** * A small long-running JVM for practicing Arthas commands. * * Compile: javac ArthasDemoApplication.java * Run: java ArthasDemoApplication */ public class ArthasDemoApplication { public static void main(String[] args) throws InterruptedException { String processName ManagementFactory.getRuntimeMXBean().getName(); System.out.println(Arthas demo started. JVM process: processName); System.out.println(Keep this window open, then attach Arthas from another terminal.); OrderService orderService new OrderService(); int sequence 1; while (true) { Order order new Order(order- sequence, 99 sequence); try { OrderResult result orderService.createOrder(order); System.out.println(LocalTime.now() success: result); } catch (IllegalStateException exception) { System.out.println(LocalTime.now() failed: exception.getMessage()); } sequence; Thread.sleep(1000); } } } class OrderService { private final InventoryService inventoryService new InventoryService(); private final PaymentService paymentService new PaymentService(); public OrderResult createOrder(Order order) { validate(order); inventoryService.reserve(order); int payableAmount calculatePayableAmount(order); String paymentId paymentService.pay(order, payableAmount); return new OrderResult(order.getId(), paymentId, payableAmount); } private void validate(Order order) { if (order.getAmount() 0) { throw new IllegalArgumentException(amount must be positive); } } private int calculatePayableAmount(Order order) { return order.getAmount() * 9 / 10; } } class InventoryService { public void reserve(Order order) { int number order.getNumber(); if (number % 5 0) { sleep(1500); // Deliberately slow: useful for trace. } if (number % 7 0) { throw new IllegalStateException(inventory is unavailable for order.getId()); } } private void sleep(long milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException exception) { Thread.currentThread().interrupt(); } } } class PaymentService { public String pay(Order order, int payableAmount) { if (ThreadLocalRandom.current().nextInt(10) 0) { throw new IllegalStateException(payment gateway timeout for order.getId()); } return pay- order.getNumber() - payableAmount; } } class Order { private final String id; private final int amount; Order(String id, int amount) { this.id id; this.amount amount; } String getId() { return id; } int getAmount() { return amount; } int getNumber() { return Integer.parseInt(id.substring(order-.length())); } } class OrderResult { private final String orderId; private final String paymentId; private final int payableAmount; OrderResult(String orderId, String paymentId, int payableAmount) { this.orderId orderId; this.paymentId paymentId; this.payableAmount payableAmount; } Override public String toString() { return OrderResult{orderId orderId , paymentId paymentId , payableAmount payableAmount }; } }终端打开到这个代码的目录下编译和启动这个代码他会持续的运行并打印 JVM 进程号cd D:\Self-Mediajavac ArthasDemoApplication.javajava ArthasDemoApplication保持这个窗口不关闭另开一个窗口启动arthasjava -jar arthas-boot.jar可以看到1和2两个进程第一个就是我们的demo运行成功如下4. 最重要的命令我们可以输入help可以查看有哪些指令然后我们来介绍几个比较重要和常用的指令命令一句话作用什么时候用dashboard实时看板看CPU、内存、 线程一上来就用看整体有没有异常thread看线程栈定位CPU飙高thread -n 3或死锁thread -bjad反编译类确认线上代码是不是最新版本watch看方法的入参、返回值、异常接口返回错或没日志时抓现场trace看方法内部调用链路的耗时接口慢查瓶颈在哪一步ognl执行表达式查看/修改静态变量查配置开关、改线上开关谨慎profilerCPU性能分析生成火焰图定位CPU热点tt记录方法调用回放和分析历史调用5. 三个核心命令watch、trace、tt5.1 watch看入参、返回值和异常watch OrderService createOrder {params, throwExp} -e -x 2 -n 5参数含义params方法参数returnObj返回值throwExp抛出的异常-x 2对象展开层级避免输出过深-n 5最多采集 5 次线上必须限制次数5.2 trace定位慢调用接口变慢时不要先猜数据库或网络问题先追踪真实方法路径trace OrderService createOrder -n 5输出会显示调用树及每个节点耗时。5.3 tt记录并分析调用现场ttTime Tunnel会保存一次方法调用现场适合偶发异常或难以复现的问题。tt -t com.example.order.OrderService createOrder查看已经记录的数据tt -l查看指定记录的入参、返回值或异常tt -i 1000线上使用tt时要注意记录对象会占用内存必须控制记录范围和次数排查结束后及时清理。6. profiler 指令生成火焰图Arthas 的profiler命令底层依赖 async-profiler。当前环境下该功能只支持 Linux 和 macOSWindows 不能使用。火焰图适合分析CPU 持续升高定位最消耗 CPU 的调用链接口执行慢分析 CPU 密集型代码大量对象创建定位内存分配热点线程阻塞分析锁竞争或等待调用不过需要注意不同问题应使用不同的采集事件。# CPU 火焰图profiler start --event cpu# 等待采集一段时间profiler status# 停止并生成 HTML 文件profiler stop --file /tmp/cpu-flamegraph.html也可以直接指定采集时长profiler start --event cpu --duration 120采集 120 秒后文件会自动生成。把生成的 HTML 文件复制到自己的电脑上然后用浏览器打开。不同事件的用途# CPU 消耗profiler start --event cpu# 网络、锁、sleep 等等待profiler start --event wall# 对象分配热点profiler start --event alloc# 锁竞争profiler start --event lock事件是否可用取决于当前 Arthas 和 async-profiler 版本。需要特别说明CPU 火焰图不能直接证明 GC 频繁alloc火焰图适合定位大量对象创建wall火焰图适合分析线程等待trace更适合分析某一个接口的单次调用耗时7. 火焰图怎么看普通火焰图中横向宽度该方法及其子调用占用的采样数量纵向高度调用栈深度越宽采样占比越高越往上调用层级越深底部调用方或线程入口顶部当前正在执行的方法例如main └── OrderService.createOrder └── PaymentService.pay └── JsonSerializer.serialize如果JsonSerializer.serialize这一块很宽说明采样期间大量 CPU 时间消耗在 JSON 序列化相关调用上。注意火焰图横轴通常不是时间轴左右位置没有特殊含义重点看色块的宽度。