[java] JVM 内存泄漏分析案例
JVM 内存泄漏分析
初始代码
public class App {private static final Map<Long, Object> productCache = new HashMap<>();private static final Random RANDOM = new Random();public static void main(String[] args) {try {while (true) {long id = System.nanoTime(); productCache.put(id, new Object());if (productCache.size() % 1_000_000 == 0) {System.out.println("The current number of cached objects: " + productCache.size());}}} catch (OutOfMemoryError e) {System.err.println("\n OOM!!! The final number of cached objects: " + productCache.size());e.printStackTrace();}}
}
编译运行
命令行在APP.java所在目录执行
# 编译
javac App.java# 运行,最大堆内存大小为 256MB,自动生成快照
java -Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/tmp/demo1-heap.hprof App
分析内存快照
使用MAT1.7.0版本,MAT入门参考


修复代码
改用带淘汰机制的缓存替换 HashMap 为 LinkedHashMap 并实现 LRU(最近最少使用)淘汰策略,限制缓存最大容量:
public class App {// private static final Map<Long, Object> productCache = new HashMap<>();private static final Random RANDOM = new Random();private static final Map<Long, Object> productCache = new LinkedHashMap<Long, Object>(1024, 0.75f, true) {@Overrideprotected boolean removeEldestEntry(Map.Entry<Long, Object> eldest) {// More than one million goods will be eliminated if they have not been used for the longest timereturn size() > 1_000_000;}};public static void main(String[] args) {try {while (true) {long id = System.nanoTime(); productCache.put(id, new Object());if (productCache.size() % 1_000_000 == 0) {System.out.println("The current number of cached objects: " + productCache.size());}}} catch (OutOfMemoryError e) {System.err.println("\n OOM!!! The final number of cached objects: " + productCache.size());e.printStackTrace();}}
}
再次编译运行发现缓存维持在1_000_000
常见内存泄漏场景及解决
根据分析结果,针对性修复:
静态集合未清理
问题:static List/Map 等容器长期持有对象引用,未及时移除(如缓存未设置过期策略)。
解决:改用弱引用集合(WeakHashMap)、设置缓存淘汰机制(如 LRU),或定期清理无效数据。
资源未关闭
问题:数据库连接、文件流、网络连接等资源未关闭,导致对象无法回收。
解决:使用 try-with-resources 自动关闭资源,或在 finally 块中显式释放。
监听器 / 回调未移除
问题:注册的监听器(如 GUI 事件、观察者模式)未注销,导致被监听对象长期引用。
解决:在对象销毁前移除监听器,或使用弱引用实现回调。
线程泄漏
问题:线程池核心线程持有大对象引用,或未正确停止的线程(如 Thread 未中断)。
解决:控制线程池核心线程数,使用 ThreadLocal 时注意清理(remove()),确保线程能正常终止。
类加载器泄漏
问题:自定义类加载器加载的类未被回收(如热部署场景),导致元空间溢出。
解决:避免长期持有类加载器引用,确保加载的类及其依赖可被 GC 回收。
