ConcurrentHashMap内存泄漏问题
问题背景
上周,同事写了一段ConcurrentHashMap的测试代码,说往map里放了32个元素就内存溢出了,我大致看了一下他的代码及运行的jvm参数,觉得很奇怪,于是就自己捣鼓了一下。首先上一段代码:
public class MapTest {
public static void main(String[] args) {
System.out.println("Before allocate map, free memory is " + Runtime.getRuntime().freeMemory()/(1024*1024) + "M");
Map<String, String> map = new ConcurrentHashMap<String, String>(2000000000);
System.out.println("After allocate map, free memory is " + Runtime.getRuntime().freeMemory()/(1024*1024) + "M");
int i = 0;
try {
while (i < 1000000) {
System.out.println("Before put the " + (i + 1) + " element, free memory is " + Runtime.getRuntime().freeMemory()/(1024*1024) + "M");
map.put(String.valueOf(i), String.valueOf(i));
System.out.println("After put the " + (i + 1) + " element, free memory is " + Runtime.getRuntime().freeMemory()/(1024*1024) + "M");
i++;
}
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable t) {
t.printStackTrace();
} finally {
System.out.println("map size is " + map.size());
}
}
}
执行时加上jvm执行参数 -Xms512m -Xmx512m ,执行结果:
Before allocate map, free memory is 483M
After allocate map, free memory is 227M
Before put 0 element, free memory is 227M
java.lang.OutOfMemoryError: Java heap space
at java.util.concurrent.ConcurrentHashMap.ensureSegment(ConcurrentHashMap.java:746)
at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1129)
at com.test.MapTest.main(MapTest.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
map size is 0
最开始的代码是没有加入一些日志打印的,当时就很奇怪,为什么只往map里放一个元素就报了OutOfMemoryError了。
于是就加了上述打印日志,发现在创建map的时候已经占用了二百多兆的空间,然后往里面put一个元素,put前都还有二百多兆,put时就报了OutOfMemoryError, 那就更奇怪了,初始化map的时候会占用一定空间,可以理解,但是只是往里面put一个很小的元素,为什么就会OutOfMemoryError呢?
排查过程
1、第一步,将第一段代码中的Map<String, String> map = new ConcurrentHashMap<String, String>(2000000000);修改为Map<String, String> map = new ConcurrentHashMap<String, String>(2000000000);,这次运行正常。(没有找到问题根因,但是以后使用ConcurrentHashMap得注意:1、尽量不初始化;2、如果需要初始化,尽量给一个比较合适的值)
2、第二步,执行时加上jvm参数-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\gc.hprof,后面两个参数主要作用是当程序内存溢出时会打印堆快照文件,已便于我们来分析内存。生成gc.hprof文件后,采用Eclipse Memory Analyzer来分析,结果如下图,可见ConcurrentHashMap中的HashEntry占用了大量内存(关于Eclipse Memory Analyzer分析待补充)。此时只是找到了哪个对象占用空间,但是为什么为出现这种情况不得而知。

3、第三步,分析ConcurrentHashMap源代码,首先,了解一下ConcurrentHashMap的结构,它是由多个Segment组成(每个Segment拥有一把锁,也是ConcurrentHashMap线程安全的保证,不是本文讨论的重点),每个Segment由一个HashEntry数组组成,出问题就在这个HashEntry上面。

4、第四步,查看ConcurrentHashMap的初始化方法,可以看出第27行,初始化了Segment[0]的HashEntry数组,数组的长度为cap值,这个值为67108864
cap的计算过程(可以针对于初始化过程进行调试)
1)initialCapacity为2,000,000,000,而MAXIMUM_CAPACITY的默认值(也即ConcurrentHashMap支持的最大值是1<<30,即230=1,073,741,824),initialCapacity的值大于MAXIMUM_CAPACITY,即initialCapacity=1,073,741,824
2)c的值计算为 initialCapacity/ssize=67108864
3)cap为 大于等于c的第一个2的n次方数 也即67108864
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
25 Segment<K,V> s0 =
26 new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
27 (HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
5、第五步,查看ConcurrentHashMap的put方法,可以看出在put一个元素的时候,
1)计算当前key的hash值,查看当前key所在的semgment有没有初始化,若果有,则执行后续的put操作。
2)如果没有去执行ensureSement()方法,而在ensureSement()方法中又会初始化了一个HashEntry数组,数组的长度和第一个初始化的Segment的HashEntry的长度一致,可以从代码行的18、19行看出。
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
9 s = ensureSegment(j);
return s.put(key, hash, value, false);
}
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
22 HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
6、这个时候能定位到是因为put一个元素的时候创建了一个长度为67103684的HashEntry数组,而这个HashEntry数组会占用67108864*4byte=256M,和上面的测试结果能对应起来。为什么数组会占用这么大的空间,很多同学可能会有疑问,那来看一下数组的初始化 而数组初始化会在堆内存创建一个HashEntry引用的数组,且长度为67103684,而每个HashEntry引用(所引用的对象都为null)都占用4个字节(参见http://www.importnew.com/18878.html)。

问题总结
1、ConcurrentHashMap初始化时要指定合理的初始化参数(当然本人做了一个小测试,指定初始化参数暂时没有发现性能上的提升,所以待各位看客自己来评估)
后记
这篇博客是本人的第一篇博客,当然有很多不足之处,如果有纰漏欢迎各位大神指出,谢谢各位!
ConcurrentHashMap内存泄漏问题的更多相关文章
- JRockit检测Tomcat内存溢出JAVA内存泄漏问题
http://blog.csdn.net/liyanhui1001/article/details/8240473 公司的一个Java应用系统上线以来,基本每1天OutOfMemoryError: P ...
- 运维-JVM监控之内存泄漏
转载:https://blog.csdn.net/zdx_csdn/article/details/71214219 jmap -heap pid查看进程堆内存使用情况,包括使用的GC算法.堆配置参数 ...
- 【知识必备】内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye
一.写在前面 对于C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针:而对于java来说,就是new出来的Object放在Heap上无法被GC回收:而这里就把我之前的一篇内存泄漏的总 ...
- Android性能优化之利用Rxlifecycle解决RxJava内存泄漏
前言: 其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所有想着查找一下资料学 ...
- Android性能优化之利用LeakCanary检测内存泄漏及解决办法
前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...
- C++的内存泄漏检测
C++大量的手动分配.回收内存是存在风险的,也许一个函数中一小块内存泄漏被重复放大之后,最后掏空内存. 这里介绍一种在debug模式下测试内存泄漏的方法. 首先在文件的开头以确定的顺序写下这段代码: ...
- 使用 Android Studio 检测内存泄漏与解决内存泄漏问题
本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...
- 【腾讯优测干货分享】Android内存泄漏的简单检查与分析方法
本文来自于Dev Club 开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d14047603a5bf1242ad01b 导语 内存泄漏问题大约是An ...
- Handler系列之内存泄漏
本篇简单的讲一下平常使用Handler时造成内存泄漏的问题. 什么是内存泄漏?大白话讲就是分配出去的内存,回收不回来.严重会导致内存不足OOM.下面来看一下造成内存泄漏的代码: public clas ...
随机推荐
- WebApi系列~StringContent与FormUrlEncodedContent
回到目录 知识点 本文是一个很另类的文章,在项目中用的比较少,但如果项目中真的出现了这种情况,我们也需要知道如何去解决,对于知识点StringContent和FormUrlEncodedContent ...
- Effective前端5:减少前端代码耦合
什么是代码耦合?代码耦合的表现是改了一点毛发而牵动了全身,或者是想要改点东西,需要在一堆代码里面找半天.由于前端需要组织js/css/html,耦合的问题可能会更加明显,下面按照耦合的情况分别说明: ...
- wampserver服务器无法启动(图标颜色不对)
1.服务器一直无法启动,图标颜色一直显示黄色,并且不可用. 2.解决办法: (1) C:\wamp\bin\apache\apache2.4.9\bin ->httpd.exe 找到该文件 ( ...
- @Autowired注解的使用
使用Spring时,通过Spring注入的Bean一般都被定义成private,并且要有getter和setter方法,显得比较繁琐,增加了代码量,而且有时会搞忘造成错误. 可以使用@Autowire ...
- 在docker容器中vi指令找不到
在使用docker容器时,有时候里边没有安装vi,敲vi命令时提示说:vi: command not found,这个时候就需要安装vi,可是当你敲apt-get install vi命令时,提示: ...
- 使用insertBefore实现insertAdjacentHTML()
Element.insertAdjacentHTML()方法由IE引入,并在HTML5中标准化,它将任意的HTML标记字符串插入到指定的元素“相邻”的位置. insertAdjacentHTML()有 ...
- 工业物联网或系统集成中应用消息队列(ActiveMQ,C#的demo)的场景全面分析
1.[连载]<C#通讯(串口和网络)框架的设计与实现> 2.[开源]C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 2.应用SuperIO(SIO)和开源跨平台物联网框 ...
- css图片精灵
<ul> <li class="top"> <em>01</em> <p><a href="http:/ ...
- Atitit.log日志技术的最佳实践attilax总结
Atitit.log日志技术的最佳实践attilax总结 1. 日志的意义与作用1 1.1. 日志系统是一种不可或缺的单元测试,跟踪调试工具1 2. 俩种实现[1]日志系统作为一种服务进程存在 [2] ...
- struts2杂记(一)——使用doubleSelect
一.前言 这段时间忙的要死,做项目,学框架,时间根本不够用,只能尽量抽出时间记录自己学过的东西. 1.1.doubleSelect 在之前学习中,我们使用过二级列表,相信很多人都理解其原理,在stru ...