1. 线上系统假死问题的典型表现与初步诊断
假死问题是Java线上系统中最令人头疼的故障之一,系统看似在运行(进程存在、端口可连接),但实际已丧失服务能力。去年我们电商大促期间就遭遇过订单服务假死,表面看JVM的CPU和内存占用都正常,但API响应完全卡死。这种问题用常规监控工具很难定位,而MAT(Memory Analyzer Tool)正是解决这类疑难杂症的利器。
假死问题通常伴随以下特征:
- 线程池满但无活跃线程处理请求
- 日志停止输出但进程未崩溃
- JMX可连接但所有操作超时
- 堆内存使用率看似正常(70%-80%)
关键提示:当系统出现"能ping通但无响应"时,第一时间保存现场!用jmap生成堆转储文件(heap dump)后再重启服务,这是后续分析的黄金数据。
2. MAT工具的核心能力与实战配置
2.1 MAT的安装与加速技巧
从Eclipse官网下载MAT时,国内开发者常遇到下载慢的问题。推荐使用清华镜像源:
https://mirrors.tuna.tsinghua.edu.cn/eclipse/mat/1.13.0/rcp/解压后建议调整MemoryAnalyzer.ini配置,将默认的-Xmx1024m改为-Xmx4g(分析大堆转储时需要更多内存)。我曾分析过一个8GB的堆转储文件,默认配置直接OOM崩溃。
2.2 堆转储文件的生成技巧
获取堆转储的两种可靠方式:
# 方式1:jmap直接生成(适合服务仍能响应命令) jmap -dump:format=b,file=heap.hprof <pid> # 方式2:添加JVM参数自动生成(适合即将崩溃的场景) -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof避坑指南:不要在生产环境用jmap -histo:live,这会触发Full GC导致服务雪崩。曾有个团队在高峰期执行此命令,直接引发连锁故障。
3. 假死问题的深度分析方法论
3.1 线程分析:死锁只是冰山一角
打开MAT的Thread Overview视图,重点观察:
- BLOCKED状态的线程数量及栈轨迹
- WAITING on condition的线程持有哪些锁
- 查找"RMI TCP Connection"等JMX相关线程是否阻塞
典型案例:某次我们发现所有Dubbo线程都阻塞在Log4j2的AsyncLogger上,原因是日志队列满导致生产者线程全部挂起。
3.2 内存泄漏的蛛丝马迹
使用Dominator Tree视图,按retained heap排序:
- 查找异常大的对象集合(如ArrayList占500MB)
- 检查缓存实现(如Guava Cache)的命中率
- 分析HashMap的加载因子(loadFactor>0.75易引发冲突)
3.3 对象关联分析的神技
对可疑对象右键选择"Path to GC Roots":
- 排除weak/soft/phantom引用
- 查看最终被哪些静态变量持有
我曾用此方法发现Spring的@Scheduled注解任务持有整个ApplicationContext,导致旧版本无法GC。
4. 典型假死场景的优化方案
4.1 线程池饥饿死锁
症状:线程池满且所有线程都在等待FutureTask完成 优化方案:
// 错误示范 executor.submit(() -> { Future<String> f = executor.submit(() -> "hello"); return f.get(); // 嵌套提交导致死锁 }); // 正确做法 CompletableFuture.supplyAsync(() -> "hello", executor1) .thenApplyAsync(s -> s+" world", executor2);4.2 锁升级引发的雪崩
当synchronized从偏向锁升级到重量级锁时,会触发STW。建议:
- 用jstack查看锁的owner地址
- 在MAT中搜索该地址对应的对象
- 改用ReentrantLock或分段锁
4.3 元空间泄漏
MAT的Classloader标签页可以:
- 统计重复加载的类数量
- 定位未卸载的WebappClassLoader
- 检查JNI全局引用
某次升级后,我们发现Jetty的类加载器滞留了300MB的元空间。
5. 长效预防机制建设
5.1 监控增强方案
在Prometheus中添加关键指标:
- pattern: 'java.lang<type=Threading><>(TotalStartedThreadCount, ThreadCount, PeakThreadCount)' name: 'jvm_threads_$1' - pattern: 'java.lang<type=OperatingSystem><>(OpenFileDescriptorCount, MaxFileDescriptorCount)' name: 'jvm_fd_$1'5.2 压测时必检项
- 用Arthas监控锁竞争:
monitor -c 5 java.lang.Object lock - 观察DirectBuffer内存:
vmtool --action getInstances --className java.nio.DirectByteBuffer --limit 10 - 检查JNI引用:
jcmd <pid> VM.native_memory summary
5.3 应急预案清单
- 保存堆转储后立即重启
- 灰度回滚时先对比类加载器数量
- 用BTrace动态注入诊断代码
经过这些优化,我们的系统假死故障从每月2-3次降到了半年内零发生。最关键的是培养团队"保存现场"的意识——就像刑事侦查中的保护现场,第一时间的堆转储往往比事后所有分析都有价值。