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 120M
After allocate map, free memory is 121M
Before put the element, free memory is 121M
After put the element, free memory is 121M
Before put the element, free memory is 121M
After put the element, free memory is 122M
Before put the element, free memory is 122M
After put the element, free memory is 122M
Before put the element, free memory is 122M
After put the element, free memory is 122M
Before put the element, free memory is 122M
After put the element, free memory is 114M
Before put the element, free memory is 114M
java.lang.OutOfMemoryError: Java heap space
map size is
at java.util.concurrent.ConcurrentHashMap.ensureSegment(Unknown Source)
at java.util.concurrent.ConcurrentHashMap.put(Unknown Source)
at com.j.u.c.tj.MapTest.main(MapTest.java:)
最开始的代码是没有加入一些日志打印的,当时就很奇怪,为什么只往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>();,这次运行正常。(没有找到问题根因,但是以后使用ConcurrentHashMap得注意:1、尽量不初始化;2、如果需要初始化,尽量给一个比较合适的值)
2、第二步,执行时加上jvm参数-Xms20124m -Xmx1024m 。发现还是同样出现问题。
3、第三步,分析ConcurrentHashMap源代码,首先,了解一下ConcurrentHashMap的结构,它是由多个Segment组成(每个Segment拥有一把锁,也是ConcurrentHashMap线程安全的保证,不是本文讨论的重点),每个Segment由一个HashEntry数组组成,出问题就在这个HashEntry上面
4、第四步,查看ConcurrentHashMap的初始化方法,可以看出,初始化了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]
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(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的长度一致。
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
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);
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个字节。
问题总结
1、ConcurrentHashMap初始化时要指定合理的初始化参数(当然本人做了一个小测试,指定初始化参数暂时没有发现性能上的提升)
ConcurrentHashMap内存溢出问题的更多相关文章
- ConcurrentHashMap内存泄漏问题
问题背景 上周,同事写了一段ConcurrentHashMap的测试代码,说往map里放了32个元素就内存溢出了,我大致看了一下他的代码及运行的jvm参数,觉得很奇怪,于是就自己捣鼓了一下.首先上一段 ...
- Java 内存区域与内存溢出
内存区域 Java 虚拟机在执行 Java 程序的过程中会把他所管理的内存划分为若干个不同的数据区域.Java 虚拟机规范将 JVM 所管理的内存分为以下几个运行时数据区:程序计数器.Java 虚拟机 ...
- jvm内存溢出分析
概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...
- 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码
程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...
- Tomcat内存溢出的三种情况及解决办法分析
Tomcat内存溢出的原因 在生产环境中tomcat内存设置不好很容易出现内存溢出.造成内存溢出是不一样的,当然处理方式也不一样. 这里根据平时遇到的情况和相关资料进行一个总结.常见的一般会有下面三种 ...
- webSphere内存溢出
有一个做了很长时间的项目,是用websphere做生产环境的,可是一旦加载的项目过多,webSphere就很傲娇的内存溢出,这是一个折腾了公司里某个前辈很久很久的问题,因为是测试版,所以各种官方文档说 ...
- java内存溢出和内存泄露
虽然jvm可以通过GC自动回收无用的内存,但是代码不好的话仍然存在内存溢出的风险. 最近在网上搜集了一些资料,现整理如下: —————————————————————————————————————— ...
- myeclipse tomcat内存溢出解决方法
Tomcat直接启动正常,通过myeclipse启动tomcat内存溢出.MyEclipse启动Tomcat无视catalina.bat中设置内存大小的问题.在 tomcat的catalina.bat ...
- 《深入理解Java虚拟机》Java内存区域与内存溢出异常
注:“蓝色加粗字体”为书本原语 先来一张JVM运行时数据区域图,再接下来一一分析各区域功能: 程序计数器 程序计数器(program Counter Register)是一块较小的内存空间,它可以 ...
随机推荐
- BZOJ2428 均分数据
2428: [HAOI2006]均分数据 Time Limit: 5 Sec Memory Limit: 128 MB Description 已知N个正整数:A1.A2.…….An .今要将它们分 ...
- 20155225 2016-2017-2 《Java程序设计》第八周学习总结
20155225 2016-2017-2 <Java程序设计>第八周学习总结 教材学习内容总结 通用API 日志API 国际化基础 规则表达式 JDK8增强功能 教材学习中的问题和解决过程 ...
- iOS代码规范之命名规范
技术博客http://www.cnblogs.com/ChenYilong/ 新浪微博http://weibo.com/luohanchenyilong 命名规范类命名 首字母大写,之 ...
- 第9月第27天 AVAssetExportSession AVAssetExportPresetMediumQuality
1. AVAssetExportPresetMediumQuality和 AVAssetExportPreset960x540 码率相差很大,视频大小也会相差很大 AVAssetExportPrese ...
- HDU 2049 不容易系列之(4)——考新郎 (错排+组合)
题目链接. Problem Description 国庆期间,省城HZ刚刚举行了一场盛大的集体婚礼,为了使婚礼进行的丰富一些,司仪临时想出了有一个有意思的节目,叫做"考新郎",具体 ...
- jquery的几种ajax方式对比
jquery的几种ajax方式对比 jquery的ajax方式有如下几种: 1. $.post(url,params,callback); 2. $.getJSON(url,params,ca ...
- Python 入门基础5 --元组、字典、集合
今日目录: 一.元组 二.字典 三.集合 四.后期添加内容 一.元组 1.定义 t1 = () # 参数为for可以循环的对象(可迭代对象) 思考: 如何定义一个只有一个值的元组? ("li ...
- Java内存模型-锁的内存语义
一 引言 在说volatile的内存语义时,讲过这样一句话:想要理解透volatile特性有一个很好的方法,就是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.所 ...
- mybatis关联查询数据模型分析——(七)
1. 数据模型分析思路 1.每张表记录的数据内容 分模块对每张表记录的内容进行熟悉,相当 于你学习系统 需求(功能)的过程. 2.每张表重要的字段设置 非空字段.外键字段 3.数据库级别表与表 ...
- MySQL管理工具MySQL Utilities — 介绍与安装(1)
MySQL Utilities介绍 MySQL Utilities 提供一组命令行工具用于维护和管理 MySQL 服务器,包括: 管理工具 (克隆.复制.比较.差异.导出.导入) 复制工具 (安装.配 ...