Java应用HashMap内存泄漏排查:原因与堆转储分析方法
分析你的Java内存泄漏问题
一、可能的泄漏根源:主HashMap的条目无限增长
看你的代码,我注意到一个关键细节:你在日志里只打印了每个NavigableMap的元素数量(MyCache.map1.get(key).size()),但从来没打印过主HashMap本身的条目数(MyCache.map1.size())。这很可能就是泄漏的核心:
每次循环中,你执行MyCache.map1.put(channel, new ConcurrentSkipListMap<>(map1Temp)),如果channel的值在每次循环中是新增的、不重复的,那么主HashMap会不断添加新的键值对,旧的条目因为被static的HashMap引用,永远不会被GC回收。哪怕每个NavigableMap的大小固定,主HashMap的条目数会持续增长,最终耗尽内存。
另外,检查你的channel变量是如何生成的:如果它和循环中的mylist元素绑定,或者每次循环生成新值,而你从来没有从主HashMap中移除过旧的channel条目,这就会导致内存泄漏。
二、如何通过JProfiler堆转储识别泄漏
按照以下步骤排查:
- 在JProfiler中加载堆转储后,先看内存占用排名,找到
HashMap$Node(主HashMap的条目)或者ConcurrentSkipListMap实例的占比。如果它们的数量异常大,就是可疑点。
- 在JProfiler中加载堆转储后,先看内存占用排名,找到
- 右键点击这些对象,选择Show retained set(查看保留集),或者追踪引用链,最终会定位到
MyCache类的static变量map1到map7——因为static变量是全局引用,会一直持有这些条目。
- 右键点击这些对象,选择Show retained set(查看保留集),或者追踪引用链,最终会定位到
- 进一步查看主HashMap的内容:在JProfiler中展开
MyCache.map1,查看它的table数组,统计其中的有效entry数量,对比你预期的数量(比如应该只有几个固定的channel)。如果entry数远大于预期,就确认是主HashMap无限增长导致的泄漏。
- 进一步查看主HashMap的内容:在JProfiler中展开
- 检查这些entry的key值(也就是
channel),看看是不是在不断新增,没有被清理。
- 检查这些entry的key值(也就是
三、代码中的隐患点修复
针对你的场景(每15分钟缓存最近15分钟的数据),你需要确保主HashMap不会积累无用的条目:
- 复用固定的channel键:如果你的业务只需要维护一组缓存(对应同一个channel),那么每次put时用同一个channel值,这样旧的NavigableMap会被新的替换,旧对象会被GC回收。
- 清理过期的channel条目:如果确实需要多组channel的缓存,那么在每次循环前,先移除主HashMap中过期的channel(比如超过15分钟未更新的),避免条目无限增长。
- 优化static临时map的使用:虽然你每次都会clear临时map,但static变量本身会一直存在,不过这不是泄漏的根源——只要clear方法正确执行,临时map的元素会被回收。不过可以考虑把临时map改为非static,放在循环内部,进一步避免潜在的引用问题。
举个修复示例,假设你只需要维护单个channel的缓存:
// 确保channel是固定值,比如提前定义为常量 private static final String FIXED_CHANNEL = "default_channel"; // 在Aggregation的循环中 MyCache.map1.put(FIXED_CHANNEL, new ConcurrentSkipListMap<>(map1Temp));
如果需要多channel缓存,添加清理逻辑:
// 在每次put新条目前,先移除过期的channel long currentTime = System.currentTimeMillis(); MyCache.map1.entrySet().removeIf(entry -> { // 假设可以从entry的value或key判断是否过期 NavigableMap<Long, String> value = entry.getValue(); return value.isEmpty() || value.lastKey() < currentTime - 15*60*1000; });
补充:关于new对象与HashMap.put的泄漏风险
你提到的new ConcurrentSkipListMap()本身没有泄漏隐患——问题出在static HashMap持有了无限增长的键值对,导致这些新创建的map无法被回收。只要你控制好主HashMap的条目数量,put操作是安全的。
内容的提问来源于stack exchange,提问作者angel_30
相关产品推荐
相关产品推荐

