检测和修复内存泄漏
识别常见的内存泄漏模式(静态集合、监听器和缓存),并进行修复
检测和修复内存泄漏 是 CoddyKit 上的免费 Java Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
什么是 Java 内存泄漏?
当对象已经不再需要,却仍然保持强可达状态,使垃圾回收器无法回收它们时,就会发生 Java 内存泄漏。堆会不断增长,直到抛出 OutOfMemoryError。
静态集合累积
保存不断增长的集合的静态字段是典型的内存泄漏来源。添加后从未移除的对象会在整个应用生命周期内保持存活。
public class Cache {
// LEAK: static list grows forever if items are never removed
private static final List<Object> items = new ArrayList<>();
public static void add(Object o) { items.add(o); }
// Fix: add a remove() method or use a bounded cache
}未注销的事件监听器
注册监听器后从未移除,会使监听器及其引用的所有对象保持存活。当监听器不再需要时,请务必注销它。
// Leak:
sensor.addListener(new DataLogger());
// Fix:
DataLogger logger = new DataLogger();
sensor.addListener(logger);
// ... when done:
sensor.removeListener(logger);未清理的 ThreadLocal 变量
在线程池中,由于线程会被重复使用,ThreadLocal 值会在任务之间继续存在。如果不清理,一个任务的数据就会泄漏到同一线程随后执行的任务中。
private static final ThreadLocal<MyContext> CTX = new ThreadLocal<>();
// Always clean up after each task:
try {
CTX.set(new MyContext(requestId));
doWork();
} finally {
CTX.remove(); // prevents leak in pooled threads
}热部署应用中的类加载器泄漏
在 Servlet 容器中,每次部署都会使用新的类加载器。如果某个类持有对旧类加载器中某个类的静态引用,整个旧类加载器及其所有类都无法被垃圾回收。
使用堆转储检测内存泄漏
使用 jmap -dump:format=b,file=heap.hprof <pid> 获取堆转储,然后在 Eclipse MAT 或 VisualVM 中打开它,以找出保留对象最大的对象及其垃圾回收根。
// Take a heap dump:
jmap -dump:live,format=b,file=heap.hprof $(jps | grep MyApp | cut -d" " -f1)
// Or trigger on OOM:
// -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprofEclipse 内存分析器(MAT)
MAT 的“泄漏嫌疑”报告会自动识别保留堆较大的对象,并显示从垃圾回收根到这些对象的路径。请先查看“支配树”,以找出占用内存最多的对象。
使用 WeakReference 避免内存泄漏
请将缓存对象或监听器对象包装在 WeakReference 中。内存压力较大时,垃圾回收器可以回收这些对象。请始终检查 get() 的结果是否为 null。
Map<String, WeakReference<Image>> imageCache = new HashMap<>();
imageCache.put("logo", new WeakReference<>(loadImage("logo.png")));
Image logo = imageCache.get("logo") != null ? imageCache.get("logo").get() : null;
if (logo == null) logo = reload("logo.png"); // re-load if GCed使用 LinkedHashMap LRU 限制缓存大小
在 LinkedHashMap 中重写 removeEldestEntry,以限制缓存大小,并自动驱逐最近最少使用的条目。
Map<String, String> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<String, String> e) {
return size() > 100; // evict when over 100 entries
}
};使用 Java 飞行记录器分析内存分配
JFR(Java 飞行记录器)能够以极低的开销记录内存分配概况。请使用 -XX:StartFlightRecording 启用它,并使用 JDK 任务控制台进行分析。
// Start a 60-second JFR recording:
java -XX:StartFlightRecording=duration=60s,filename=rec.jfr MyApp
// Or via jcmd:
jcmd <pid> JFR.start duration=60s filename=rec.jfr修复内存泄漏:检查清单
请检查:静态集合、未注销的监听器、线程池中的 ThreadLocals、连接或流泄漏(使用 try-with-resources)、内部类对外部对象的引用,以及没有驱逐机制的缓存。
快速检查
抛出 OutOfMemoryError 时,哪个 JVM 标志会自动转储堆?
回顾
Java 中常见的内存泄漏来源包括:静态集合、未注销的监听器、线程池中的 ThreadLocals,以及无界缓存。请使用堆转储和 MAT 进行检测,并使用 WeakReferences、有界数据结构,以及在使用完毕后始终移除引用来修复问题。
常见问题解答
「检测和修复内存泄漏」课时是免费的吗?
是的 — 「检测和修复内存泄漏」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「检测和修复内存泄漏」这节课中我会学到什么?
识别常见的内存泄漏模式(静态集合、监听器和缓存),并进行修复 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「检测和修复内存泄漏」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。