递归的隐藏代价:空间复杂度深度解析与时空权衡
2026/8/12 19:43:04
堆转储(Heap Dump)是 JVM 在某一时刻整个堆内存的快照,以.hprof文件形式保存。它包含:
💡 内存泄漏的本质:本该被回收的对象,因被意外强引用而无法释放,持续占用堆内存。堆转储能让你“看到”这些不该存在的对象及其引用链。
# JVM 启动参数:当发生 OOM 时自动生成 heap dump -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps/# 使用 jcmd(JDK 自带) jcmd <pid> GC.run_finalization jcmd <pid> VM.gc jcmd <pid> GC.run # 可选:先触发一次 GC,排除可回收对象干扰 jcmd <pid> VM.heap_dump /tmp/app.hprof # 或使用 jmap(已 deprecated,但仍可用) jmap -dump:live,format=b,file=/tmp/app.hprof <pid>✅ 建议加
live参数:只 dump 存活对象,减少文件体积,聚焦真实问题。
要定位内存泄漏,关键在于回答三个问题:
| 工具 | 特点 |
|---|---|
| Eclipse MAT(Memory Analyzer Tool) | 最强大、最常用,可视化好,支持 OQL 查询 |
| VisualVM | JDK 自带,轻量级,适合快速查看 |
| JProfiler / YourKit | 商业工具,功能全面,适合深度调优 |
| 命令行(jhat + 浏览器) | 老旧,不推荐 |
✅ 推荐使用Eclipse MAT(免费开源):https://www.eclipse.org/mat/
.hprof文件MAT 会自动解析并生成Leak Suspects Report(内存泄漏嫌疑报告)—— 这是第一线索!
📌 Retained Heap ≠ Shallow Heap:
- Shallow Heap:对象自身占用内存(不含引用对象)
- Retained Heap:对象 + 所有仅被它引用的对象总内存 →这才是关键指标!
public class MemoryLeakExample { // 静态集合 = GC Root!所有加入的对象都无法被回收 private static final List<String> CACHE = new ArrayList<>(); public static void main(String[] args) throws InterruptedException { while (true) { CACHE.add("Leak data: " + System.currentTimeMillis()); Thread.sleep(100); } } }ArrayList占用巨大 Retained HeapMemoryLeakExample.CACHE(静态字段)引用public class EventManager { private static final List<EventListener> listeners = new ArrayList<>(); public static void addListener(EventListener listener) { listeners.add(listener); // 添加后从未 remove! } // 忘记提供 removeListener() 方法 } // 某个 Activity 或临时对象注册了监听器 public class TempComponent { public TempComponent() { EventManager.addListener(this::onEvent); } private void onEvent() { /* ... */ } }TempComponent实例EventManager.listeners→TempComponentpublic class BadThreadLocal { private static final ThreadLocal<byte[]> local = ThreadLocal.withInitial(() -> new byte[1024 * 1024]); // 1MB public void process() { local.get(); // 使用 // 忘记调用 local.remove()! } } // 在线程池中反复调用 ExecutorService pool = Executors.newFixedThreadPool(10); while (true) { pool.submit(() -> new BadThreadLocal().process()); }byte[]数组Thread.threadLocals→ThreadLocalMap→byte[]ThreadLocal在线程复用场景下必须remove(),否则内存泄漏!MAT 支持类似 SQL 的查询语言,快速筛选对象:
-- 查找所有 ArrayList 实例,按 retained size 降序 SELECT * FROM java.util.ArrayList ORDER BY retainedHeapSize DESC -- 查找包含特定字符串的对象 SELECT * FROM java.lang.String s WHERE s.value.toString().contains("Leak")WeakHashMap或设置容量上限。remove()。| 步骤 | 关键动作 |
|---|---|
| 1️⃣ 生成堆转储 | -XX:+HeapDumpOnOutOfMemoryError或jcmd |
| 2️⃣ 打开分析 | 使用 Eclipse MAT |
| 3️⃣ 定位大对象 | 查看Dominator Tree,按 Retained Heap 排序 |
| 4️⃣ 追溯引用链 | “Path to GC Roots” → 找出强引用源头 |
| 5️⃣ 修复代码 | 清理无效引用、改用弱引用、限制缓存等 |