内存完全由Java ConcurrentHashMap使用(在Tomcat下)
这是一个内存堆栈(用作缓存),只包含一个静态ConcurrentHashMap(CHM)。
所有传入的HTTP请求数据都存储在此ConcurrentHashMap中。 并且有一个异步调度程序进程从同一个ConcurrentHashMap获取数据,并在将它们存储到数据库后删除key.value。
该系统运行良好且流畅,但只是按照以下标准发现,内存已充分利用(2.5GB)并且所有CPU时间都用于执行GC:
-concurrent http命中1000 / s
– 在15分钟内保持相同的并发命中
每次写入数据库时,异步进程都会记录CHM的剩余大小。 CHM.size()保持在Min:300到Max:3500左右
我以为这个应用程序存在内存泄漏。 所以我使用Eclipse MAT来查看堆转储。 运行可疑报告后,我从MAT获得了这些评论:
由“org.apache.catalina.loader.StandardClassLoader @ 0x853f0280”加载的“org.apache.catalina.session.StandardManager”的一个实例占用2,135,429,456(94.76%)个字节。 内存在“”由“”加载的“java.util.concurrent.ConcurrentHashMap $ Segment []”的一个实例中累积。
3,646,166 instances of java.util.concurrent.ConcurrentHashMap$Segment retain >= 2,135,429,456 bytes.
和
Length # Objects Shallow Heap Retained Heap 0 3,646,166 482,015,968 >= 2,135,429,456
我将上面的长度0转换为CHM内的空长记录(每次调用CHM.remove()方法)。 它与数据库内的记录数一致,创建此转储时,数据库中有3,646,166条记录
奇怪的情况是 :如果我暂停压力测试,堆内存中的利用率将逐渐降至25MB。这需要大约30-45分钟。 我重新模拟了这个应用程序,曲线看起来类似于下面的VisualVM图:
inheritance人提出的问题:
1)这看起来像是内存泄漏吗?
2)每次删除调用remove(Object key, Object value)
从CHM中删除 ,删除的对象是否得到GC?
3)这与GC设置有关吗? 我添加了以下GC参数但没有帮助:
-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:GCTimeRatio=19 -XX:+PrintGCTimeStamps -XX:ParallelGCThreads=6 -verbose:gc
4)非常感谢任何解决这个问题的想法! 🙂
新 5)可能因为我的所有参考都是硬参考吗? 我的理解是,只要HTTP会话结束,所有那些非静态的变量现在都可用于GC。
新注意我尝试用ehcache 2.2.0替换CHM,但是我遇到了相同的OutOfMemoryException问题。 我想ehcache也在使用ConcurrentHashMap。
服务器规格:
-Xeon Quad内核,8个线程。
-4GB内存
-Windows 2008 R2
-Tomcat 6.0.29
这个问题让我错了7天! 最后我发现了真正的问题! 以下是我尝试但未能解决OutOfMemoryexception的任务:
– 使用concurrenthashmap更改为ehcache。 (原来ehcache也在使用ConcurrentHashMap)
– 更改Soft Reference的所有硬引用
– 根据Heinz M. Kabutz博士的建议,将AbstractMap与concurrnetHashMap一起使用
百万美元的问题真的是“为什么30-45分钟后,内存开始重新发布到堆池?”
实际的根本原因是因为还有其他东西仍然存在实际的变量会话,而罪魁祸首是tomcat中的http会话仍处于活动状态! 因此,即使http会话已完成,但如果超时设置为30分钟,则tomcat会将会话信息保留30分钟,然后JVM才能进行GC会话。 将测试超时设置更改为1分钟后立即解决问题。
$tomcat_folder\conf\web.xml 1
希望这能帮助那些有类似问题的人。
我认为你正在使用过多的 会话数据 , 这些 数据 不会同时存在 于内存中 。 试试这个:
-
编辑
bin/setenv.sh
或在Tomcat启动程序上设置JVM参数的位置:附加
-Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true
例如
# Default Java options if [ -z "$JAVA_OPTS" ]; then JAVA_OPTS="-server -Djava.awt.headless=true -XX:MaxPermSize=384m -Xmx1024m -Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true" fi
-
编辑
conf/context.xml
,在之前添加:
重新启动Tomcat,您的问题应该消失,因为它将使用文件系统存储您的会话 。
在我的视图设置中, session-timeout = 1
是一种掩盖问题根源的解决方法,并且在大多数实际需要足够大的session-timeout
应用程序中无法使用。 我们( Bippo的)应用程序通常session-timeout
为2880
分钟,即2天。
参考: Tomcat 7.0会话管理器配置
1)这看起来像是内存泄漏吗?
是的,如果应用程序继续将对象放在地图中并且从不删除它们,那么很可能是内存泄漏。
2)每次删除调用remove(Object key,Object value)从CHM中删除a,删除的对象是否得到GC?
如果没有对它们有引用的实时(运行)线程,则只能对对象进行垃圾回收。 地图只是一个有对象引用的地方。 可能仍有其他地方引用同一个对象。 但是将对象保留在地图中会阻止它被垃圾收集。
3)这与GC设置有关吗?
没有; 如果引用了一个对象,则不能进行垃圾回收; 你如何调整垃圾收集器并不重要。
当然,现在回答已经太晚了,但仅限于其他通过搜索找到这个问题的人。 它可能有用。
这两个链接非常有用
https://issues.apache.org/bugzilla/show_bug.cgi?id=50685
http://wiki.apache.org/tomcat/OutOfMemory
简而言之,在大多数情况下,它是一个错误的测试或测试软件。 当某些自定义软件打开URL时,如果此软件无法管理http会话,则tomcat会为每个请求创建新会话。 例如,可以使用简单的代码进行检查,该代码可以添加到JSP中。
System.out.println("session id: " + session.getId()); System.out.println("session obj: " + session); System.out.println("session getCreationTime: " + (new Date(session.getCreationTime())).toString()); System.out.println("session.getValueNames().length: " + session.getValueNames().length);
如果从负载测试的角度来看一个用户的会话ID是相同的,那么没问题,如果每个请求都生成新的会话ID,这意味着测试软件不能很好地管理会话,测试结果不代表真实用户的负载。
对于某些应用程序,session.getValueNames()。length也很重要,因为例如,当普通用户工作时,它保持不变,但是当负载测试软件执行相同操作时,它会增长。 这也意味着,负载测试软件并不能很好地代表真正的工作负载。 在我的情况下,session.getValueNames()。普通用户的长度约为100,但是10分钟后qwith负载测试软件大约是500,最后系统崩溃时出现相同的OutOfMemory错误,MAT显示相同:
org.apache.catalina.loader.StandardClassLoader @ 0x853f0280“占用2,135,429,456(94.76%)字节。
如果你得到这个例外并使用spring boot version 1.4.4 RELEASE或更低版本,请在几分钟内设置属性“server.session-timeout”的值,而不是他们建议的值(秒),以便堆上的会话将是及时清理。 或者您可以使用EmbeddedServletContainerCustomizer的bean,但提供的值将在几分钟内设置。
示例(会话超时10分钟):server.session-timeout = 10(在属性文件中设置)container.setSessionTimeout(10,TimeUnit.SECONDS); (在EmbeddedServletContainerCustomizer中设置)