CodeGym /课程 /JAVA 25 SELF /内存分析工具:jmap、jvisualvm

内存分析工具:jmap、jvisualvm

JAVA 25 SELF
第 64 级 , 课程 3
可用

1. jmap:命令行魔法

自动垃圾回收固然很好,但即便是 Java 应用也可能“泄漏”(OutOfMemoryError)、运行缓慢,或因内存使用不当而出现意外卡顿。原因可能包括:

  • 内存泄漏(memory leaks):本应被回收的对象仍然“存活”。
  • 集合中数据量过大。
  • 缓存逻辑错误。
  • 资源未释放(例如线程、文件)。

你的目标是学会快速定位这些问题。否则,你可能会从程序员变成“缺陷制造者”!

什么是 jmap?

jmap 是 JDK 自带的实用工具。它可以获取正在运行的 Java 进程的内存“快照”(heap dump)。随后可在其它工具中打开该转储,查看哪些对象占用了内存、各有多少、由谁引用等。

Heap dump 就像你堆的照片:包含其中所有对象、它们的类型、关系以及大小。

如何使用 jmap

查找进程 PID

首先需要知道运行你的 Java 应用的进程标识(PID)。可以通过多种方式获取:

通过 jps(JDK 自带的另一个工具):

jps -l

你会看到所有 Java 进程及其 PID 列表。

也可以通过任务管理器(Task Manager)或 Linux 上的 ps

生成内存转储

jmap -dump:format=b,file=heap.bin <PID>
  • format=b —— 二进制格式(适合分析)。
  • file=heap.bin —— 保存转储的文件名。
  • <PID> —— 进程标识。

示例:

jmap -dump:format=b,file=heap.bin 12345

拿到 heap dump 之后怎么办?
通常会在图形化工具中分析转储(例如 jvisualvmEclipse MAT),因为手动在二进制文件里找对象属于高难度操作,还有点“自虐”。

jmap 的其他功能

查看内存统计:

jmap -heap <PID>

显示堆信息:大小、所用 GC、状态。

类直方图:

jmap -histo <PID>

显示各类的统计:每种类型对象的数量和总大小。

输出示例:

# 对象 数量 字节
1
[C
1200 48000
2
java.lang.String
1000 32000
3
java.util.HashMap
200 12800

这已经能提供线索:如果某种类型的对象突然达到数百万——就值得警惕了。

2. jvisualvm:可视化内存分析器

jvisualvm 是一款随 JDK 提供的图形化程序(在 bin 目录中)。它可以连接本地(有时也能连接远程)Java 进程,实时监控、生成 heap dump、查看内存、线程、GC、CPU 统计,甚至对代码执行进行剖析。

如果你喜欢点点鼠标、看看漂亮的图表——这就是你的工具。

如何启动 jvisualvm

在命令行(或通过 Windows 快捷方式):

jvisualvm

会打开一个窗口,你会看到所有本地 Java 进程的列表。

jvisualvm 的主要功能

实时内存监控

  • 在左侧列表中选择一个进程。
  • 切换到 Monitor 标签。
  • 你会看到 heap、CPU、线程及类数量的使用图表。

示例:

jvisualvm-monitor

Heap Dump(内存快照)

  • Monitor 面板上点击 Heap Dump
  • 几秒后会出现带有转储分析的标签页。
  • 你会看到所有类、对象数量及其总大小的列表。

查找“重量级”对象

  • 按大小或数量排序。
  • 如果发现某个类型(例如 ArrayListString)占用过多内存——就该深入排查。

引用分析(Reference Graph)

  • 点击某个类——查看是谁引用该类的对象。
  • 可以追溯到根因:为什么对象没有被垃圾回收。

线程分析

  • Threads 标签——显示所有线程及其状态。
  • 可以发现“卡死”的线程或过于活跃的线程。

性能剖析

  • Profiler 标签——衡量哪些方法最耗时或最耗内存。
  • 这是更深入的分析,但查找内存泄漏通常只需前面的步骤。

3. 实战:分析一次内存泄漏

带泄漏的示例代码

import java.util.ArrayList;
import java.util.List;

public class MemoryLeakDemo {
    // 静态集合——老套路!
    private static final List<String> bigList = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1_000_000; i++) {
            bigList.add("String number " + i);
            if (i % 100_000 == 0) {
                System.out.println("已添加: " + i);
                Thread.sleep(500); // 留点时间给分析
            }
        }
        System.out.println("完成! 不要关闭应用, 打开 jvisualvm.");
        Thread.sleep(600_000); // 10 分钟——用于分析
    }
}

发生了什么?

  • 我们创建了一个静态列表,并向其中添加了一百万条字符串。
  • 添加结束后程序会“挂起”(等待),以便你可以连接分析工具进行检查。

用 jvisualvm 进行分析

  1. 运行该程序。
  2. 打开 jvisualvm 并选择该进程。
  3. Monitor 标签查看内存图表。
    • 一切正常时,GC 之后内存会回落。
    • 若存在泄漏,内存会持续增长。
  4. 生成 Heap Dump
  5. 查看哪些对象占用最多内存。
    • 在我们的示例中——java.util.ArrayListjava.lang.String
  6. 点击对象——查看是谁引用它。
    • 你会看到是静态字段 bigList

如何修复?

  • 删除集合中不需要的元素,限制其增长。
  • 当大型对象不再需要时,将引用置空。
  • 不要把大型集合放在 static 中,除非它们必须常驻。

4. 其他工具:Eclipse MAT、jconsole

Eclipse Memory Analyzer (MAT)

Eclipse MAT 是免费但功能强大的 heap dump 分析工具。借助它可以看到哪些对象占用内存、哪些对象又在“保留”其他对象——即所谓的 retained set,从而准确定位泄漏所在。

该工具还能生成针对可疑对象(leak suspects)的详细报告,并能从容处理数 GB 级的大型转储。

使用也很简单:用 jmapjvisualvm 生成内存转储,在 MAT 中打开,点击 Leak Suspects Report——即可得到已经高亮潜在问题的报告。

jconsole

jconsole 是一个简单易用的 JVM 实时监控工具。它展示内存使用、CPU 负载、线程数量以及垃圾回收行为。

与更高级的 VisualVM 不同,jconsole 不分析 heap dump,但非常适合快速诊断——当你需要一眼看清应用当前状态时尤为方便。

5. 内存分析中的常见错误

错误 1:给错进程做转储。 一台机器上常常跑着多个 Java 应用,很容易弄错 PID。请先通过 jps 核对,确保分析的是你的程序。

错误 2:在没有泄漏的地方期待看到“泄漏”。 GC 不一定会立刻回收对象——有时只有在 Full GC 之后内存才明显回落。不要慌,如果一次之后内存没有释放——看看趋势。

错误 3:忽视了静态/缓存对象的影响。 许多泄漏源于对象被保存在 static 字段或全局集合中。检查是谁在引用“重量级”对象——往往就是 static

错误 4:不做动态剖析。 Heap dump 很好,但有时需要观察一段时间内的内存增长(jvisualvm 的图表)。如果内存稳定地增长——就需要进一步排查。

错误 5:没有移除监听器/事件处理器。 如果你订阅了事件却没有取消订阅——即使你不再使用,该对象也会一直“存活”在内存中。

建议:不要害怕尝试这些工具!做转储、看图表、点击对象——只有这样你才能真正“掌握”自己程序的内存,并快速找到问题。

1
任务
JAVA 25 SELF, 第 64 级, 课程 3
已锁定
使用 jmap 获取内存转储 — 熊的数字快照 🐻
使用 jmap 获取内存转储 — 熊的数字快照 🐻
1
任务
JAVA 25 SELF, 第 64 级, 课程 3
已锁定
通过 jmap 查看类统计 – 数字动物园清点 🐒🐘🐆
通过 jmap 查看类统计 – 数字动物园清点 🐒🐘🐆
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION