首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >线上应用内存使用突然增加70M,90% 研发踩坑,原因竟然是 Arthas 火焰图脚本少一行代码

线上应用内存使用突然增加70M,90% 研发踩坑,原因竟然是 Arthas 火焰图脚本少一行代码

原创
作者头像
用户12591013
修改2026-07-09 17:50:50
修改2026-07-09 17:50:50
20
举报
文章被收录于专栏:踩坑集踩坑集

前言

线上定时采集性能火焰图,每次跑完进程物理内存固定上涨 70M 且不会自动回落;重复采集内存不再增长,重启服务后重新采集又会复现内存占用上涨。

排查 GC、业务代码、JVM 参数、中间件连接池全部无异常,问题潜伏很久无人定位根因。

复现验证后找到核心漏洞:自动化脚本仅停止 profiler 采样,未执行 stop 卸载 Arthas Agent,注入的代理组件常驻 JVM,内存无法释放。

本文还原故障、拆解 Arthas Attach 底层原理、UDS 通信与信号唤醒机制,附带精简无业务标识脚本,吃透诊断工具底层 JVM 细节。

一、故障现象与精简问题脚本

1. 故障表现

  1. 全新无流量应用,执行火焰图采集后,物理内存永久上涨 70MB;
  2. 多次重复采集,内存不再新增;服务重启后采集,内存再次上涨 70M;
  3. 堆内存、GC 日志正常,上涨为堆外常驻内存,FullGC 无法回收。

2. 问题脚本

代码语言:javascript
复制
# 采集参数
serviceTag=$1
collectDuration=60

# 获取Java进程PID
PID=$(ps aux | grep spring.application.name | grep -v grep | awk '{print $2}')
[ -z "$PID" ] && exit 1

# 自动拉取Arthas启动脚本
[ ! -f ./as.sh ] && curl -O xxxx/as.sh -s
chmod +x ./as.sh

# 开启性能采样
./as.sh -c "profiler start" $PID
sleep $collectDuration

# 停止采样,导出火焰图
cmdOutput=$(./as.sh -c "profiler stop --format html" $PID)
flameFile=$(echo "$cmdOutput" | grep -oP '(?<=file: ).*\.html')

# 重命名图表
mv $flameFile ${serviceTag}_flame.html

致命缺陷:仅关闭性能采样器,缺少stop卸载 Agent 指令。

3. 验证根因

手动分步测试:

  1. attach 进程,执行profiler start+profiler stop后直接退出;
  2. 进程内存固定上涨 70M,永久占用;
  3. 补充执行./as.sh -c "stop" $PID,Agent 完整卸载,内存回落。

stop 执行输出佐证:

代码语言:javascript
复制
Resetting all enhanced classes ...
Affect(class count:0,method count:0) cost in 1 ms, listenerId:0
Arthas Server is going to shutdown...

二、底层原理:仅停 profiler 无法释放内存

1. Arthas Attach 完整注入流程

依托 JDK Attach API + Instrumentation 动态注入,4 步流程:

  1. VirtualMachine.attach(PID) 发起连接;
  2. UDS 文件 + SIGQUIT 信号带外唤醒 JVM Attach Listener;
  3. 加载arthas-agent.jar,运行agentmain启动常驻服务;
  4. Agent 内置 Netty、Profiler 采样、字节码增强组件,占用固定堆外内存。

profiler stop只关闭采样功能,Agent 主体常驻;仅stop命令会完整卸载所有组件。

2. Attach 底层:Unix Domain Socket + SIGQUIT 唤醒

  1. UDS 通信:本机依靠/tmp/.java_pid{PID}文件通信,不走 TCP/IP;套接字文件、Agent 资源不会随客户端脚本自动清理,仅同系统用户可访问。
  2. 信号唤醒机制
    • 客户端创建.attach_pid{PID}标记文件,发送SIGQUIT
    • JVM 信号线程检测标记文件,启动 Attach Listener 建立 UDS 通道;
    • 无标记文件执行kill -3仅打印线程栈,不会注入 Agent。

3. UDS vs 普通 TCP Socket 对比

表格

特性

Unix Domain Socket(Arthas)

普通 TCP Socket

寻址

本地文件路径

IP + 端口

范围

仅本机

跨机器

协议栈

无 TCP/IP,性能更高

完整 TCP 协议栈

资源回收

文件不会自动删除

连接断开自动释放端口

4. Attach 安全边界

属于 JDK 官方标准诊断接口,非后门:

  1. 权限限制:仅同操作系统用户 /root 可 attach 目标进程;
  2. 能力:支持字节码重定义、内存读取、原生采样加载;
  3. 生命周期:Agent 独立于外部脚本,客户端退出不会停止 JVM 内代理。

三、修复方案:精简修复脚本,采集后强制卸载 Agent

修复后核心精简代码

代码语言:javascript
复制
# 开启采样
./as.sh -c "profiler start" $PID
sleep $collectDuration
# 停止采样导出图表
cmdOutput=$(./as.sh -c "profiler stop --format html" $PID)
flameFile=$(echo "$cmdOutput" | grep -oP '(?<=file: ).*\.html')

# 新增关键代码:卸载Agent,释放70M常驻内存
./as.sh -c "stop" $PID

mv $flameFile ${serviceTag}_flame.html

stop 命令资源回收流程

  1. 重置所有增强类,清空字节码监听;
  2. 关闭 Agent 内置 Netty 服务;
  3. 销毁 Profiler 采样器,释放堆外缓冲区;
  4. 卸载 arthas-agent,JVM 回收全部占用内存;
  5. 清理 Attach 通信临时资源。

四、线上运维避坑总结

  1. Arthas 自动化采集脚本必须搭配 profiler stop + stop,缺 stop 会造成 Agent 常驻、内存永久占用;
  2. 单次 Attach 固定占用约 70M 堆外内存,重复 attach 不会叠加内存占用;
  3. UDS 套接字文件进程退出不会自动删除,定时脚本可增加清理逻辑,避免 attach 失败;
  4. JDK8/11/17 全版本存在该机制,定时诊断脚本必须补齐资源释放逻辑;
  5. Attach 依靠信号 + UDS 本机通信,进程异常退出极易残留 Agent 资源。

结语

大量线上隐性内存上涨故障,根源并非业务代码,而是自动化诊断工具遗漏资源释放逻辑。

多数研发、运维只会复制 Arthas 采集脚本,不了解 Attach 注入、Agent 生命周期、UDS 底层通信,每次采集都会给业务进程埋下隐形内存隐患。

吃透 JDK 官方 Attach 底层机制、信号唤醒、Agent 完整生命周期,才能写出无残留、安全可靠的自动化诊断脚本,彻底规避火焰图、trace、watch 等工具带来的内存占用问题。


原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、故障现象与精简问题脚本
    • 1. 故障表现
    • 2. 问题脚本
    • 3. 验证根因
  • 二、底层原理:仅停 profiler 无法释放内存
    • 1. Arthas Attach 完整注入流程
    • 2. Attach 底层:Unix Domain Socket + SIGQUIT 唤醒
    • 3. UDS vs 普通 TCP Socket 对比
    • 4. Attach 安全边界
  • 三、修复方案:精简修复脚本,采集后强制卸载 Agent
    • 修复后核心精简代码
    • stop 命令资源回收流程
  • 四、线上运维避坑总结
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档