0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

visualvm heapdump

0
Last updated at Posted at 2025-06-20

理解你的困惑!初次使用 VisualVM 分析 heapdump 文件,面对海量数据确实容易不知所措。别担心,我来一步步带你解读那些看似复杂的界面,帮你找到关键线索 🔍

📊 1. 打开 Heapdump 后的主要视图区域

  • Overview (概览):
    • Summary (摘要): 显示堆的总大小、类总数、实例总数、ClassLoader 总数等基本信息。这是你首先应该看的地方。
    • Biggest Objects by Retained Size (按保留大小排序的最大对象): 列出对整个堆内存影响最大的几个对象(通常是那些持有大量其他对象引用的对象)。这是查找内存泄漏嫌疑犯的黄金位置
    • Actions (操作): 提供保存、另存为、查找类、执行 OQL 查询等按钮。
  • Classes (类视图): 这是最常用、最核心的分析界面。
    • 列出堆中加载的所有类。
    • 默认按 Instances (实例数量) 降序排列。
    • 关键列:
      • Class Name: 类名 (全限定名)。
      • Instances: 该类的实例在堆中的总数量。
      • Shallow Size: 该类的所有实例自身占用的内存总和 (不包含它们引用的其他对象占用的内存)。想象成一个空盒子的重量。
      • Retained Size: 该类所有实例被垃圾回收后,能释放出的内存总量。这包括它们自身的 Shallow Size,加上所有只有通过这些实例才能访问到的(被它们独占或共享主导权)其他对象占用的内存。这是衡量内存影响的关键指标!想象成盒子本身加上里面装的所有东西的总重量。
  • Instances (实例视图):Classes 视图中双击某个类,会跳转到该类所有实例的列表。
    • 显示每个实例的 Shallow SizeRetained Size
    • 可以查看某个实例的 References (引用) 和 GC Root Path (到 GC Root 的路径)。
  • OQL Console (OQL 控制台): 使用类似 SQL 的语法 (Object Query Language) 查询堆中的对象。非常强大,用于精确查找特定对象。
  • Threads (线程视图 - 可选): 如果 heapdump 包含线程信息 (通常包含),可以查看线程状态和调用栈。对分析死锁或某些特定线程导致的内存问题有帮助。

🔍 2. 如何看懂并分析 - 实战步骤

  1. 快速扫描 Overview -> Summary:

    • 确认堆总大小是否符合你的预期?(比如你设置了 -Xmx1G, 这里显示接近 1G, 那说明堆确实快满了)。
    • 看看 Instances 总数和 Classes 总数,对堆的复杂度有个大致印象。
  2. 锁定嫌疑人 (Classes 视图): 这是最关键的步骤!

    • Instances 排序: 点击 Instances 列头降序排列。找那些实例数量异常多的类。常见嫌疑犯:
      • 基础类型集合: char[], byte[], java.lang.String, java.lang.Object[]
      • Java 集合框架: java.util.HashMap$Node, java.util.ArrayList, java.util.concurrent.ConcurrentHashMap$Node
      • 应用特定类: 你自己代码中创建大量实例的类。
    • Retained Size 排序: 点击 Retained Size 列头降序排列。这是最有价值的排序方式!它直接告诉你哪些类的实例(以及它们所持有的整个对象图)占据了堆内存的绝大部分。内存泄漏的元凶通常在这里名列前茅!
    • 结合两者看:
      • 一个类实例数很多但 Retained Size 不大 (比如大量小 String),可能不是主要问题,但也不容忽视。
      • 一个类实例数不多但 Retained Size 极大 (比如一个巨大的缓存 HashMap 持有大量对象),这绝对是重点怀疑对象!
      • 一个类实例数 Retained Size 巨大,那基本就是主要问题所在了。
  3. 深入调查具体类 (Instances 视图):

    • Classes 视图中双击你锁定的高 Instances 或高 Retained Size 的类。
    • Instances 视图中,同样可以按 Retained Size 排序,找出该类中占用内存最多的单个实例
    • 右键单击一个高 Retained Size 的实例 -> Show In Nearest GC Root: 这是杀手锏!它能显示这个对象为什么不能被垃圾回收——即从它到 GC Root (垃圾回收根节点,如活动线程栈帧中的局部变量、静态变量、JNI 引用等) 的引用链。这个引用链就是它“存活”不被回收的原因。
      • 仔细分析这条路径:引用链是否合理?对象是否应该还存活?是否存在意料之外的静态引用、缓存引用、监听器未注销等问题?
    • 右键单击实例 -> Show Objects -> By Outgoing References: 查看这个实例引用了哪些其他对象。这有助于理解它为什么 Retained Size 那么大 (它持有了什么大对象或很多小对象)。
  4. 利用 OQL Console 进行精确查询:

    • 如果你知道要找的类名、属性值、或者想查询特定特征的对象 (如所有长度大于 1000 的 String),OQL 非常高效。
    • 常用 OQL 示例:
      • 查找所有 MyAppClass 的实例:
        select * from com.example.MyAppClass
        
      • 查找长度大于 1024 的 String:
        select s from java.lang.String s where s.value.length > 1024
        
      • 查找某个特定属性值的对象 (假设 MyClass 有属性 id):
        select * from com.example.MyClass m where m.id == "problematic_id"
        
    • 查询结果会显示在下方,双击结果同样可以查看引用链等。
  5. 查看 Threads (如果怀疑线程相关):

    • 检查是否有大量线程处于 BLOCKEDWAITING 状态 (可能死锁)。
    • 查看线程的调用栈 (Stack Trace),看是否有线程持有大量对象或卡在某个可能累积数据的循环中。

🎯 3. 分析内存泄漏/大对象的典型思路

  1. 识别大对象:
    • Classes 视图按 Retained Size 降序排,看前几名是谁。
    • OverviewBiggest Objects by Retained Size
  2. 追溯存活原因:
    • 对可疑的大对象或实例数异常多的类的实例,右键 -> Show In Nearest GC Root
    • 分析 GC Root Path: 这个引用链是否合理?对象是否应该被释放了但还被强引用 (如静态 Map、未取消的监听器、未关闭的资源) 持有?这是找到泄漏根源的关键!
  3. 理解对象内容:
    • 右键 -> Show Objects -> By Outgoing References 看它持有哪些具体数据。
    • 对于集合类 (如 HashMap, ArrayList),查看其内部数组 (table/elementData) 的大小和内容。
  4. 对比多个 Heapdump (强烈推荐):
    • 如果应用还在运行,隔一段时间 (比如间隔 10 分钟或触发一次 Full GC 后) 再取一个 heapdump。
    • 在 VisualVM 中同时打开两个 heapdump。
    • 使用 Tools -> Open in New Tab 打开第二个 heapdump。
    • Classes 视图中,使用 Calculate Retained Sizes 功能 (通常在视图工具栏上有个计算器图标)。这需要点时间计算。
    • Retained Size DiffInstances Diff 排序: 这能直观地显示出在两次 dump 之间,哪些类的新增实例数最多 (Instances Diff),哪些类新增占用的内存最多 (Retained Size Diff)内存泄漏的源头通常在这些差异巨大的类中!

⚠ 4. 重要提示和常见陷阱

  • Retained Size 是关键: Shallow Size 往往意义不大,Retained Size 才能真正反映对象对内存的压力。分析时时刻关注它。
  • GC Roots 是核心: 理解对象为什么不能被回收,必须看它到 GC Root 的路径 (Show In Nearest GC Root)。
  • 基础类型数组 (char[], byte[]) 通常是内容载体: 它们经常是 StringByteBuffer 等内部使用的。看到它们排前面,通常意味着有大量的字符串数据或原始数据 (如图片缓冲) 在堆里。接着去找谁持有这些数组 (通常是 String 或你的业务对象)。
  • 集合类 (HashMap, ArrayList 等) 是常见容器: 它们本身 Shallow Size 不大,但 Retained Size 可能巨大,因为它们持有大量元素对象。分析时要看集合内部的内容 (By Outgoing References)。
  • (something)<unreachable objects>: 在引用视图里可能会看到这些,表示引用关系是弱引用、软引用等,或者对象已经是不可达状态 (等待被回收)。通常不是主要关注点。
  • 分析需要上下文: Heapdump 是一个静态快照。你需要结合应用的业务逻辑、代码、以及问题现象 (如 OOM 错误信息、监控图表显示的内存持续增长) 来分析。问问自己:这个巨大的对象/这么多实例是合理的吗?它们应该在这个时间点还存在吗?
  • 工具只是辅助: VisualVM 帮你找到线索 (大对象、异常多的实例、可疑的 GC Root 路径),最终的解决方案需要你根据线索去检查代码逻辑。

📌 总结一下你的操作流程

  1. 打开 heapdump,扫一眼 Overview -> Summary
  2. 进入 Classes 视图,按 Retained Size 降序排列,盯住最上面那几个「内存巨头」。
  3. 双击可疑类,进入 Instances 视图,按 Retained Size 降序找出「罪魁祸首」实例。
  4. 右键该实例 -> Show In Nearest GC Root,像侦探一样追踪它的引用链 🕵️‍♂️。
  5. (可选但强力) 取第二个 heapdump,对比差异,锁定增长点。
  6. (可选) 用 OQL 精确打击特定对象。

刚开始分析时,重点关注 Retained Size 最高的前5-10个类,以及它们到 GC Root 的路径。 不要试图理解堆里的每一个对象。内存问题往往是由少数几个「大块头」或「数量惊人的小对象」导致的。找到它们,理解它们为什么存在,你就成功了一大半!💪 遇到具体可疑对象时,可以随时再来问分析思路。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?