线上 JVM 故障秒解:Arthas 高阶用法与全链路定位实战指南

张开发
2026/4/21 7:27:20 15 分钟阅读

分享文章

线上 JVM 故障秒解:Arthas 高阶用法与全链路定位实战指南
线上服务突然CPU飙高100%、接口超时频发、FullGC每隔几分钟一次、线程死锁导致服务卡死、出现异常却没有打印日志重启服务后问题消失却找不到根因下次还会复现传统的jstack、jmap、jhat工具不仅操作繁琐还需要重启服务、无法热修改代码、线上环境权限受限根本无法应对分钟级的线上故障。Arthas作为阿里巴巴开源的Java诊断利器无需重启服务、无需修改代码、全场景覆盖JVM故障排查是每个Java工程师必备的线上排障神器。Arthas核心工作原理与前置准备核心工作原理Arthas的核心是利用JVM的Instrumentation API在目标JVM进程启动后动态挂载Agent通过字节码增强技术修改已加载类的字节码实现方法执行监控、入参出参捕获、类热替换等能力全程无需停止目标进程无需修改业务代码对业务的侵入性极低。快速安装与启动使用官方一键安装脚本直接在目标服务器执行curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar执行后会列出当前服务器所有运行的Java进程输入目标进程对应的序号即可完成attach进入Arthas交互控制台。项目环境依赖本文所有实例基于以下环境构建?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.4/version relativePath/ /parent groupIdcom.jam/groupId artifactIddemo/artifactId version0.0.1-SNAPSHOT/version namedemo/name descriptionDemo project for Arthas Troubleshooting/description properties java.version17/java.version mybatis-plus.version3.5.6/mybatis-plus.version fastjson2.version2.0.52/fastjson2.version guava.version33.1.0-jre/guava.version springdoc.version2.5.0/springdoc.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.alibaba.fastjson2/groupId artifactIdfastjson2/artifactId version${fastjson2.version}/version /dependency dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version${guava.version}/version /dependency dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version${springdoc.version}/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.32/version scopeprovided/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project基础表结构实例使用MySQL 8.0表结构如下CREATE TABLE t_order ( order_id varchar(64) NOT NULL COMMENT 订单ID, user_id varchar(64) NOT NULL COMMENT 用户ID, amount decimal(10,2) NOT NULL COMMENT 订单金额, status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态 0-待支付 1-已支付 2-已取消, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (order_id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT订单表;核心业务代码package com.jam.demo.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; /** * 订单实体 * author ken * date 2026-03-16 */ Data TableName(t_order) Schema(description 订单实体) public class Order { TableId(type IdType.ASSIGN_ID) Schema(description 订单ID) private String orderId; Schema(description 用户ID) private String userId; Schema(description 订单金额) private BigDecimal amount; Schema(description 订单状态) private Integer status; Schema(description 创建时间) private LocalDateTime createTime; Schema(description 更新时间) private LocalDateTime updateTime; }package com.jam.demo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.jam.demo.entity.Order; import org.apache.ibatis.annotations.Mapper; /** * 订单Mapper接口 * author ken * date 2026-03-16 */ Mapper public interface OrderMapper extends BaseMapperOrder { }package com.jam.demo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.jam.demo.entity.Order; /** * 订单服务接口 * author ken * date 2026-03-16 */ public interface OrderService extends IServiceOrder { /** * 根据订单ID查询订单 * param orderId 订单ID * return 订单实体 */ Order getOrderById(String orderId); }package com.jam.demo.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.jam.demo.entity.Order; import com.jam.demo.mapper.OrderMapper; import com.jam.demo.service.OrderService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * 订单服务实现类 * author ken * date 2026-03-16 */ Slf4j Service public class OrderServiceImpl extends ServiceImplOrderMapper, Order implements OrderService { Override public Order getOrderById(String orderId) { LambdaQueryWrapperOrder queryWrapper new LambdaQueryWrapper(); queryWrapper.eq(Order::getOrderId, orderId); return this.getOne(queryWrapper); } }package com.jam.demo.controller; import com.jam.demo.entity.Order; import com.jam.demo.service.OrderService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; /** * 订单控制器 * author ken * date 2026-03-16 */ Slf4j RestController RequestMapping(/order) Tag(name 订单管理, description 订单相关接口) public class OrderController { Autowired private OrderService orderService; /** * 查询订单详情 * param orderId 订单ID * return 订单详情 */ GetMapping(/detail) Operation(summary 查询订单详情, description 根据订单ID查询订单详情) public Order getOrderDetail(RequestParam String orderId) { log.info(开始查询订单详情订单ID{}, orderId); Order order orderService.getOrderById(orderId); log.info(查询订单详情完成订单ID{}, orderId); return order; } }package com.jam.demo.controller; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 死锁复现控制器 * author ken * date 2026-03-16 */ Slf4j RestController RequestMapping(/deadlock) Tag(name 死锁复现, description 死锁复现接口) public class DeadlockController { private static final Object LOCK_A new Object(); private static final Object LOCK_B new Object(); /** * 触发死锁 */ GetMapping(/trigger) Operation(summary 触发死锁, description 触发线程死锁用于Arthas死锁检测演示) public String triggerDeadlock() { log.info(开始触发死锁); Thread thread1 new Thread(() - { synchronized (LOCK_A) { log.info(线程1持有LOCK_A); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error(线程1中断, e); } synchronized (LOCK_B) { log.info(线程1持有LOCK_B); } } }, Thread-0); Thread thread2 new Thread(() - { synchronized (LOCK_B) { log.info(线程2持有LOCK_B); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error(线程2中断, e); } synchronized (LOCK_A) { log.info(线程2持有LOCK_A); } } }, Thread-1); thread1.start(); thread2.start(); log.info(死锁触发完成); return 死锁已触发; } }Arthas高阶核心用法一、热代码替换线上bug秒修复无需重启服务热替换是Arthas最核心的能力之一支持在线修改已加载类的方法体无需重启服务分钟级修复线上bug。这里明确区分两个易混淆的核心命令retransform基于Arthas字节码增强框架实现支持叠加增强不会重置之前的监控增强仅支持修改方法体不支持新增字段/方法/修改方法签名是线上热修复的首选redefine直接加载外部编译好的class文件替换JVM已加载类会重置所有Arthas之前做的增强适用场景为完全替换类的字节码完整热修复实战线上OrderController的getOrderDetail方法存在bug传入空orderId会触发异常无需重启服务通过以下步骤修复反编译获取源代码jad --source-only com.jam.demo.controller.OrderController /tmp/OrderController.java修改源代码修复bug编辑/tmp/OrderController.java在方法内添加参数校验逻辑GetMapping(/detail) Operation(summary 查询订单详情, description 根据订单ID查询订单详情) public Order getOrderDetail(RequestParam String orderId) { log.info(开始查询订单详情订单ID{}, orderId); if (!StringUtils.hasText(orderId)) { log.error(订单ID不能为空); throw new IllegalArgumentException(订单ID不能为空); } Order order orderService.getOrderById(orderId); log.info(查询订单详情完成订单ID{}, orderId); return order; }获取类加载器Hash值sc -d com.jam.demo.controller.OrderController | grep classLoaderHash编译修复后的Java文件将上一步获取的hash值替换到-c参数后mc -c 1be6f5c0 /tmp/OrderController.java -d /tmp热加载修复后的Class文件retransform /tmp/com/jam/demo/controller/OrderController.class验证修复结果调用/order/detail?orderId接口会返回规范的参数异常而非未捕获的空指针修复完成全程无服务重启。热替换核心限制仅支持修改方法体不支持新增类的字段、方法、构造函数不支持修改类的继承关系、实现的接口、方法签名不支持修改注解、枚举值等类的元信息JVM重启后热修改会失效永久修复需更新代码重新发布二、全链路性能追踪接口超时根因秒定位针对线上接口超时、慢调用、偶现异常等问题Arthas提供了trace、watch、tt三大核心命令无需新增日志即可全链路观测方法执行的完整上下文。1. trace全链路耗时追踪trace命令用于追踪方法内部的调用链路统计每个节点的耗时占比精准定位慢调用代码行高阶用法如下基础追踪排除JDK内部方法减少无关输出trace -j com.jam.demo.controller.OrderController getOrderDetail执行后调用接口会输出完整调用链路与耗时分布---ts2026-03-16 10:00:00;thread_namehttp-nio-8080-exec-1;id23;is_daemontrue;priority5;TCCLorg.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader1be6f5c0 ---[1200ms] com.jam.demo.controller.OrderController:getOrderDetail() ---[0.23ms] org.slf4j.Logger:info() #24 ---[0.05ms] org.springframework.util.StringUtils:hasText() #25 ---[1195ms] com.jam.demo.service.impl.OrderServiceImpl:getOrderById() #29 ---[0.18ms] org.slf4j.Logger:info() #31 ---[0.02ms] return #32条件过滤仅追踪耗时超过1000ms的慢调用trace -j com.jam.demo.controller.OrderController getOrderDetail #cost 1000多方法递归追踪同时追踪Controller与Service层的方法trace -j -E com.jam.demo.controller.OrderController|com.jam.demo.service.impl.OrderServiceImpl getOrderDetail|getOrderById2. watch方法上下文全观测watch命令用于观测方法的入参、出参、返回值、异常对象甚至方法内部的局部变量无需新增日志即可获取完整执行上下文全场景观测同时捕获方法执行前、正常返回、异常抛出、执行结束四个节点的上下文watch com.jam.demo.controller.OrderController getOrderDetail {params,returnObj,throwExp} -b -e -s -f条件过滤仅观测指定入参的调用watch com.jam.demo.controller.OrderController getOrderDetail {params,returnObj} params[0].equals(123456) -s内部变量观测查看方法内部的局部变量值watch com.jam.demo.service.impl.OrderServiceImpl getOrderById {target, #queryWrapper} -s3. tt时间隧道偶现问题终极解决方案ttTimeTunnel命令会记录方法的每一次调用的完整上下文包括入参、出参、异常、耗时、线程信息支持事后回放、重新调用完美解决线上偶现问题无法复现的痛点开启调用记录tt -t com.jam.demo.controller.OrderController getOrderDetail查看所有记录的调用tt -l查看指定调用的详细信息index为记录的序号tt -i 1001回放指定调用用当时的入参重新执行方法完美复现问题tt -i 1001 -p三、线程问题终极定位CPU飙高、死锁、阻塞一键排查Arthas的thread命令基于JVM的ThreadMXBean实现比传统jstack更强大支持实时CPU排序、线程状态过滤、死锁自动检测无需手动分析堆栈。1. CPU飙高快速定位线上服务CPU使用率100%通过以下步骤30秒定位根因查看CPU占用Top3的线程直接输出线程堆栈thread -n 3输出结果会直接显示高CPU线程的堆栈信息定位到具体代码行http-nio-8080-exec-2 Id24 cpuUsage80.2% RUNNABLE at com.jam.demo.service.impl.OrderServiceImpl.calculateOrderAmount(OrderServiceImpl.java:88) at com.jam.demo.service.impl.OrderServiceImpl.getOrderById(OrderServiceImpl.java:30) at com.jam.demo.controller.OrderController.getOrderDetail(OrderController.java:29)2. 死锁自动检测无需手动分析jstack堆栈一键自动检测死锁thread -b如果存在死锁会直接输出死锁线程、持有锁对象、等待锁对象、完整堆栈信息Found one Java-level deadlock: Thread-1: waiting to lock monitor 0x00007f8a9b0c0000 (object 0x0000000700a00000, a java.lang.Object), which is held by Thread-0 Thread-0: waiting to lock monitor 0x00007f8a9b0c0008 (object 0x0000000700a00008, a java.lang.Object), which is held by Thread-13. 线程状态过滤查看所有处于阻塞状态的线程定位线程卡顿根因thread --state BLOCKED四、内存与GC问题深度排查OOM、FullGC频繁根因定位针对线上FullGC频繁、内存泄漏、OOM等问题Arthas提供了完整的排查工具链无需重启服务实时查看内存与GC状态动态修改JVM参数。1. 全局状态实时查看dashboard命令实时刷新JVM整体运行状态是故障排查的第一步dashboard输出内容包括堆内存eden区、survivor区、老年代的容量与使用率非堆内存元空间、代码缓存的使用情况GC统计YoungGC、FullGC的次数与总耗时线程统计活跃线程数、守护线程数、峰值线程数CPU使用率进程CPU、系统CPU使用率2. GC详情统计查看GC的详细统计信息判断GC是否异常gc3. 堆转储与内存泄漏分析线上出现内存泄漏、FullGC频繁生成堆转储文件用于深度分析heapdump --live /tmp/heapdump.hprof--live参数仅dumpFullGC后仍存活的对象排除可回收的垃圾对象大幅减小dump文件大小更精准定位内存泄漏。生成的hprof文件可通过MAT、JProfiler等工具分析找到占用内存最多的对象定位泄漏根因。4. 动态修改JVM参数无需重启服务动态修改可管理的JVM参数# 查看所有JVM参数 vmoption # 开启OOM时自动生成堆dump vmoption HeapDumpOnOutOfMemoryError true # 修改元空间最大大小 vmoption MaxMetaspaceSize 512m五、类与类加载问题排查类冲突、代码未生效一键定位线上出现NoClassDefFoundError、ClassNotFoundException、代码发布后未生效等问题通过Arthas的sc、sm、jad、classloader命令可快速定位。1. sc搜索已加载的类查看JVM中是否加载了目标类以及类的详细信息# 搜索所有Order相关的类 sc *Order* # 查看类的详细信息包括类加载器、源码位置、注解等 sc -d com.jam.demo.controller.OrderController2. jad反编译已加载的类查看JVM中实际运行的源代码排查代码未生效、类冲突问题jad --source-only com.jam.demo.controller.OrderController3. classloader类加载器分析查看类加载器的层级结构、加载的类数量、URL排查双亲委派破坏、类冲突问题# 查看所有类加载器统计信息 classloader # 查看类加载器层级结构 classloader -t # 查看指定类加载器加载的所有类 classloader -c 1be6f5c0 -l六、进阶黑科技火焰图、动态日志、OGNL万能执行1. profiler火焰图性能瓶颈终极定位Arthas集成了async-profiler可生成CPU、内存、锁的火焰图无需重启服务线上直接使用精准定位性能瓶颈# 启动CPU火焰图采集时长30秒 profiler start --duration 30 --event cpu # 查看采集状态 profiler status # 停止采集生成HTML格式火焰图 profiler stop --format html生成的火焰图可直接在浏览器打开横向宽度代表CPU占用时间纵向代表调用栈一眼定位高耗时方法。2. 动态修改日志级别线上排查问题需要DEBUG日志无需重启服务动态修改日志级别# 查看指定类的日志配置 logger -n com.jam.demo.controller.OrderController # 修改指定类的日志级别为DEBUG logger -n com.jam.demo.controller.OrderController -l DEBUG # 修改根日志级别为INFO logger -l INFO3. OGNL高阶用法线上万能执行器Arthas支持OGNL表达式可执行任意Java代码调用Spring Bean、获取静态变量、执行方法是线上排查的万能工具获取Spring容器中的Bean调用业务方法ognl org.springframework.web.context.ContextLoadergetCurrentWebApplicationContext().getBean(orderServiceImpl).getOrderById(123456)获取类的静态变量ognl com.jam.demo.controller.DeadlockControllerLOCK_A执行数据库查询ognl org.springframework.web.context.ContextLoadergetCurrentWebApplicationContext().getBean(orderMapper).selectById(123456)JVM线上故障全链路定位黄金流程通用排查总流程分场景详细排查流程1. CPU飙高排查流程2. 内存泄漏/FullGC频繁排查流程3. 接口超时/慢调用排查流程4. 死锁/线程阻塞排查流程线上真实故障实战案例案例一线上CPU飙高100%5分钟定位修复现象线上服务CPU使用率持续100%接口响应超时告警触发重启服务后10分钟复现。排查过程Arthas attach目标进程执行dashboard看到进程CPU使用率98%单线程占用85%CPU。执行thread -n 1定位到高耗时代码在OrderServiceImpl的calculateOrderAmount方法for循环终止条件写错i--写成i导致死循环。通过jad反编译、mc编译、retransform热修复CPU使用率立即降至10%以下接口恢复正常。发布正式版本修复问题彻底解决。案例二线上FullGC频繁10分钟定位内存泄漏现象线上服务每3分钟触发一次FullGC老年代回收后使用率仍达85%内存持续上涨最终OOM。排查过程执行dashboard看到老年代使用率2分钟内从50%涨到95%FullGC频繁。执行heapdump --live生成堆转储文件MAT分析发现100万Order对象占用1GB内存被静态List持有。通过ognl命令清空静态List老年代使用率立即降至20%FullGC停止。修复代码移除静态集合发布正式版本问题彻底解决。案例三线上接口偶现超时tt命令完美定位现象线上订单查询接口偶现超时每天出现3-5次无法稳定复现本地测试正常无异常日志。排查过程执行tt -t记录接口所有调用等待1小时后捕获到超时调用index1008耗时1200ms。执行tt -i 1008 -p回放调用trace追踪发现数据库查询耗时1190ms。watch查看SQL语句发现orderId字段为varchar类型传入数字类型导致隐式类型转换索引失效全表扫描。修复代码统一参数类型发布正式版本问题彻底解决。线上使用避坑指南与最佳实践核心避坑点热替换避坑热修改前必须备份原class文件生产环境禁止修改核心框架类排查完成后执行reset命令重置所有增强避免性能损耗。安全规范生产环境禁止使用stop命令会重置所有增强类禁止长时间运行trace、tt等命令避免占用过多JVM资源heapdump必须加--live参数避免磁盘IO打满禁止执行危险的ognl表达式修改业务数据。性能影响单命令性能损耗低于1%但同时开启多个增强命令会导致损耗上升排查完成后必须执行reset重置。版本兼容JDK 9需开启--enable-native-access参数SpringBoot 3.x需使用Arthas 3.6.0版本兼容Jakarta EE。最佳实践故障排查先执行dashboard整体了解JVM状态再针对性排查。偶现问题优先使用tt命令记录上下文事后回放复现。慢调用优先使用trace命令定位耗时节点逐层递归排查。CPU飙高优先使用thread -n命令定位高耗时代码行。内存泄漏优先使用heapdump生成堆转储文件结合MAT分析。排查完成后必须执行reset重置所有增强quit退出Arthas。总结Arthas作为Java线上故障排查的神器覆盖了JVM故障的全场景从CPU、内存、线程、类加载到接口性能、代码热修复全程无需重启服务无需修改代码极大提升了线上故障排查的效率。

更多文章