Java 生产环境故障排查与性能分析指南

OOM 崩溃 -> 查 HPROF

慢/卡/CPU高 -> 查 JFR

0. 核心概念与分析目标

1 Java HPROF 文件分析指南

1.1 什么是 HPROF 文件?(文件介绍)

HPROF 是 Java 堆内存转储(Heap Dump)文件的标准后缀名。

  • 本质:它是一张“快照” (Snapshot)。它记录了在 JVM 崩溃或手动导出那一瞬间,Java 堆内存中所有存活对象的状态。
  • 包含内容
    • 对象实例:内存里有哪些对象(String, HashMap, User对象等)。
    • 引用关系:谁在引用谁(A 引用了 B,导致 B 无法被回收)。
    • 对象大小:每个对象占用了多少字节。
    • 类信息:类加载器、静态变量等。
  • 不包含内容:通常不包含对象被分配时的堆栈信息(除非使用特殊的 Agent),也不包含 CPU 使用情况。

1.2 文件来源:如何获取 HPROF?

参考 OOM 生产持久化,K8S + OSS等方案,这里说手动的方式

当发现内存缓慢上涨但还未崩溃时,手动导出,命令 (需进入 Pod)

# 查找 Java 进程 PID
jcmd
# 导出 Dump
jcmd <PID> GC.heap_dump /tmp/manual-dump.hprof

1.3 MAT 查看方式与核心视图

假设你使用 Eclipse MAT 打开了文件,以下是必须关注的三个核心视图:

第一步:概览与泄漏报告

打开文件后,MAT 会弹出一个向导,直接选择 “Leak Suspects Report” (泄漏疑点报告)

  • 作用:MAT 会自动通过算法计算,告诉你:“有个地方占了 80% 的内存,嫌疑最大的是这个 ArrayList”。
  • 价值:80% 的 OOM 问题看这个报告就能解决。
    image-20251209155130453
第二步:Dominator Tree (支配树) —— 最重要

点击工具栏上的按钮进入。

  • 展示内容:列出内存中最大的对象。
  • 排序:默认按 Retained Heap (深堆/保留大小) 倒序排列。
  • 怎么看:排在第一行的通常就是导致 OOM 的元凶(比如一个巨大的 Cache,或者一个无限增长的 Queue)。
第三步:Histogram (直方图)
  • 展示内容:按“类”分组,统计每个类有多少个实例。
  • 怎么看:如果你发现 java.lang.Stringchar[] 有几百万个,虽然单个很小,但加起来很大。这通常意味着日志过多缓存设计不当循环拼接字符串

1.4 分析技巧:如何定位问题?

核心概念:Shallow vs Retained Heap

  • Shallow Heap (浅堆):对象本身占用的内存(比如一个 ArrayList 对象本身只有几十字节)。
  • Retained Heap (深堆):对象被回收后,能释放的总内存(这个 ArrayList 里面存了 100万个 User,那它的深堆就是几百 MB)。
  • 分析口诀找 Retained Heap 最大的对象,它就是内存黑洞。

技巧 A:Path to GC Roots (顺藤摸瓜)

当你发现了一个不该存在的大对象(比如一个应该被销毁的 UserContext),你想知道“是谁抓着它不放?”

  1. 在 Dominator Tree 中选中该对象。
  2. 右键 -> Path to GC Roots -> exclude all phantom/weak/soft etc. references (排除软弱虚引用,只看强引用)。
  3. 结果:MAT 会展示一条引用链,比如:ThreadLocal -> Map -> UserContext。你就知道是 ThreadLocal 没清理导致的。

2 JFR 文件 (Java Flight Recorder)

2.1 什么是 JFR 文件?(文件介绍)

JFR (Java Flight Recorder) 是 JVM 内置的性能监控和故障诊断工具。

  • 本质:它像飞机的黑匣子一样,连续记录 JVM 运行一段时间内的所有事件。它不是“一瞬间”的快照,而是一段“录像”。
  • 文件后缀.jfr
  • 核心特性极低开销 (Overhead < 1%)。基于事件采样机制,专为生产环境 7x24 小时运行设计。
  • 主要分析内容
    1. Method Profiling (CPU):哪些方法在消耗 CPU(采样)。
    2. Wall Clock (Latency):线程在“等待”什么(锁、IO、Sleep)。
    3. Memory Allocation (TLAB):对象分配速率(谁在制造垃圾),而不是存活对象。
    4. JVM Internals:GC 暂停时间、JIT 编译、类加载。
    5. Thread Dumps:定时的线程快照。

2.2 文件来源:如何获取 JFR?

EKS + DataKit 环境中,获取方式非常灵活:

A. 通过 观测云平台下载

如果您开启了 Continuous Profiler:

  1. 登录观测云 APM 界面。
    image-20251209154618928
  2. 找到感兴趣的时间段(比如 CPU 飙高那分钟)。
  3. 点击右上角的 Download Recording (下载 JFR)。
B. 命令行按需开启 (排查特定问题)

手动抓取某 60 秒的数据:

# 1. 找到 Java 进程 PID
jcmd
# 2. 开启录制 (持续 60秒,存到 /tmp)
jcmd <PID> JFR.start duration=60s filename=/tmp/my-recording.jfr

2.3 分析技巧与排查流程 (Workflow)

拿到 .jfr 文件后,不要盲目看,请根据“症状”选择分析路径:

场景一:CPU 飙高 (High CPU)
  • 工具选择:IDEA 或 JMC。
  • JMC 路径Method Profiling (方法分析) -> Count (排序)。
  • 分析技巧
    1. “Hot Methods”:看哪个方法占用的 Samples 最多。
    2. 辨别类型
      • 如果是业务方法(如 OrderService.calc):检查死循环、复杂算法。
      • 如果是 G1Refine / GC 相关:说明内存压力大,CPU 被 GC 吃掉了(转去查内存分配)。
      • 如果是 C2 Compiler:刚启动时的正常热身。
场景二:应用响应慢,但 CPU 不高 (Latency / Slowness)
  • 工具选择:JMC 是首选。
  • JMC 路径
    1. 看左侧 Automated Analysis Results:有没有红色的 Synchronized LocksSocket I/O
    2. Threads 视图:勾选 Socket ReadJava Blocking
  • 分析技巧
    • Socket Read:如果耗时很长,查该线程连接的 IP 是哪里(数据库?Redis?第三方?)。
    • Monitor Blocked:如果看到红色的阻塞条,说明发生了锁竞争。查看 Lock Instances 找是谁持有了锁。
场景三:排查频繁 GC / 内存分配过快 (Allocation Pressure)
  • 注意:JFR 不擅长看“内存泄漏”(那是 HPROF 的事),但它擅长看“谁在制造垃圾”。
  • JMC 路径Memory -> TLAB Allocations
  • 分析技巧
    • 查看 Allocation by Class:通常是 char[] (String) 或 byte[]
    • 查看 Stack Trace:看是哪行代码在疯狂 new 对象。
    • 优化:减少这些临时对象的创建,就能降低 GC 频率,从而降低 CPU。

2.4 关键指标解读备忘 (Cheatsheet)

在 JMC 的“自动分析结果”页面,关注以下警告:

  • 🔴 Synchronized Locks:死锁或严重的锁竞争。
  • 🔴 Socket I/O:网络调用极慢。
  • 🟠 G1 GC Pause:GC 停顿时间超过了预期。
  • 🟠 Code Cache Full:JIT 编译器没内存用了,性能会退化。
  • 🟠 Stackdepth Truncated:调用栈太深被截断了(提示你需要把 stackdepth 调大到 256)。

3. 常用分析工具 (Toolbox)

工具名称 适用文件类型 推荐场景 特点 下载地址
Eclipse Memory Analyzer (MAT) .hprof OOM 必选 业界标准,擅长分析大内存,拥有强大的“支配树”和“泄漏报告”。 官方下载地址
JDK Mission Control (JMC) .jfr 性能调优必选 Oracle/OpenJDK 官方工具,由“自动分析”功能,能给出评分和建议。 Eclipse JMC 官方版
IntelliJ IDEA (Ultimate) .hprof / .jfr 日常快速查看 界面友好,直接关联源码,适合开发人员快速定位热点。
VisualVM Both 轻量级查看 简单直观,适合查看基础指标,深度分析能力弱于 MAT/JMC。

3. 排查与分析流程 (Workflow)

场景 A:应用发生 OOM (OutOfMemoryError)

  1. 获取文件
    • 通过 -XX:+HeapDumpOnOutOfMemoryError 自动生成。
    • 文件路径通常为启动参数配置的 -XX:HeapDumpPath (如 /tmp 或挂载卷)。
  2. 使用 MAT 分析
    • 第一步:打开文件,选择 “Leak Suspects Report”(泄漏疑点报告)。
      image-20251209155248600
    • 第二步:查看 Dominator Tree。按 Retained Heap(深堆)倒序排列。
    • 第三步:找到占比最大的对象,右键 -> Path to GC Roots -> exclude all phantom/weak/soft etc. references
    • 结论:如果发现某个 ListMapThreadLocal 占用了 80% 内存,且无法回收,即为内存泄漏源头。

场景 B:应用 CPU 飙高或响应慢 (Performance Issue)

  1. 获取文件
    • Datadog 用户:直接在 APM Profiler 界面下载 .jfr 文件。
    • 手动获取:使用 jcmd <pid> JFR.start duration=60s filename=myrecording.jfr
  2. 使用 JMC 分析
    • 第一步 (看宏观):点击左侧 Automated Analysis Results。关注红色/橙色警告(如“Synchronized Locks”、“Socket I/O”)。
    • 第二步 (看 CPU):点击 Method Profiling。按 Count 排序,找到最热的方法(Hotspot)。
    • 第三步 (看延迟):点击 Threads 视图,勾选 Java BlockingSocket Read,查看线程是否被数据库或网络阻塞。
  3. 使用 IDEA 分析
    • 直接拖入 .jfr 文件,查看 Flame Graph (火焰图)
    • 宽顶原则:寻找最顶层且最宽的色块,直接点击跳转到代码。

4. 关键指标解读备忘 (Cheatsheet)

HPROF 常见嫌疑人

  • char[] / String:通常是日志打印过多、XML/JSON 解析生成的大量临时字符串。
  • Object[]:通常是 ArrayListHashMap 等集合类持有大量对象。
  • ClassLoader:如果出现大量 ClassLoader,可能是元空间(Metaspace)泄漏(常见于动态代理、热部署)。

JFR 常见嫌疑人

  • SocketRead / SocketWrite:网络 IO 慢(数据库、Redis、第三方 API)。
  • Monitor Blockedsynchronized 锁竞争严重。
  • G1NewAllocator:年轻代分配过快,通常意味着代码在循环中创建了大量临时对象。
  • Throwable / Exception:业务代码在用异常控制流程,或底层连接不断失败重试。

5. 工具下载地址 (Resources)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐