[java] JVM 内存泄漏分析案例

JVM 内存泄漏分析

初始代码

java 复制代码
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入门参考


修复代码

改用带淘汰机制的缓存替换 HashMapLinkedHashMap 并实现 LRU(最近最少使用)淘汰策略,限制缓存最大容量:

java 复制代码
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) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<Long, Object> eldest) {
            // More than one million goods will be eliminated if they have not been used for the longest time
            return 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 回收。

相关推荐
weixin_440730501 分钟前
python+request实现接口-小结
开发语言·python
QQ_21696290966 分钟前
【项目编号:project95315】SpringBoot公共自习室管理系统:座位预约、房间管理、签到核销、公告规则完整实战
java·spring boot·后端
ZJU_统一阿萨姆25 分钟前
【算子开发】扫描(Scan)与前缀和
开发语言·arm开发·架构·系统架构·硬件架构
-凌凌漆-26 分钟前
【C语言】结构体typedef struct与struct的区别
c语言·开发语言
嵌入式阿蔡29 分钟前
面试高频考点 01:volatile / 中断 / 堆栈 八股精讲
java·面试·职场和发展·嵌入式实时数据库
秋饼44 分钟前
LangChain4j + Java 实现企业级 Text-to-SQL 智能问数系统:从自然语言到安全可控的数据洞察
java·ai·技术分享·后端开发
Escalating_xu1 小时前
【C++ STL简介】从六大组件到容器、迭代器与算法协作
java·c++·算法
for_ever_love__1 小时前
PySpark学习: 数据输出
开发语言·python·学习·spark
何以解忧,唯有..1 小时前
Python os模块详解:文件与目录操作指南
java·服务器·python
言乐61 小时前
Python实现自动拉人脚本示例
开发语言·windows·python·django·pygame