内存完全由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  

希望这能帮助那些有类似问题的人。

我认为你正在使用过多的 会话数据这些 数据 不会同时存在 于内存中 。 试试这个:

  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 
  2. 编辑conf/context.xml ,在之前添加:

        

重新启动Tomcat,您的问题应该消失,因为它将使用文件系统存储您的会话

在我的视图设置中, session-timeout = 1是一种掩盖问题根源的解决方法,并且在大多数实际需要足够大的session-timeout应用程序中无法使用。 我们( Bippo的)应用程序通常session-timeout2880分钟,即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中设置)