这是一个内存堆栈(用作缓存),它只包含一个静态ConcurrentHashMap(CHM)。
所有传入的HTTP请求数据都存储在此ConcurrentHashMap中。并且有一个异步调度程序进程从同一个ConcurrentHashMap获取数据,并在将它们存储到数据库后删除key.value。
此系统运行良好且流畅,但只是按照以下标准发现,内存已充分利用(2.5GB)并且所有CPU时间都用于执行GC:
-concurrent http命中1000 / s
- 在15分钟内保持相同的并发命中
asynch进程每次写入数据库时都会记录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图:
提出问题:
1)这看起来像是内存泄漏吗?
2)每次删除调用remove(Object key, Object value)
以从CHM中删除<key:value>
,该删除的对象是否会获得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 core,8个线程。
-4GB内存
-Windows 2008 R2
-Tomcat 6.0.29
答案 0 :(得分:10)
这个问题让我错了7天!最后我发现了真正的问题!以下是我尝试但未能解决OutOfMemory异常的任务:
- 使用concurrenthashmap更改为ehcache。 (事实证明ehcache也使用ConcurrentHashMap)
- 更改所有对Soft Reference的硬引用
的建议,将AbstractMap与concurrnetHashMap一起使用百万美元的问题真的是“为什么30-45分钟后,内存开始释放回堆池?”
实际的根本原因是因为还有其他东西仍然存在实际的变量会话,而罪魁祸首是tomcat中的http会话仍处于活动状态!因此,即使http会话已完成,但如果超时设置为30分钟,则tomcat会将会话信息保留30分钟,然后JVM才能进行GC会话。将超时设置更改为1分钟后,问题立即解决。
$tomcat_folder\conf\web.xml
<session-config>
<session-timeout>1</session-timeout>
</session-config>
希望这会帮助那些有类似问题的人。
答案 1 :(得分:9)
我认为您正在使用太多 会话数据 一次不适合 在内存中 。试试这个:
编辑bin/setenv.sh
或在Tomcat启动器上设置 JVM args 的任何地方:
附加-Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true
e.g。
# 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
添加此内容之前修改</Context>
:
<Manager className="org.apache.catalina.session.PersistentManager"
maxIdleBackup="60" maxIdleSwap="300">
<Store className="org.apache.catalina.session.FileStore"/>
</Manager>
重新启动Tomcat,您的问题应该消失,因为它会使用文件系统来存储您的会话。
在我看来,设置session-timeout = 1
是一个解决方法,它掩盖了问题的根源,并且在大多数实际需要足够大session-timeout
的应用中无法使用。我们的(Bippo)个应用通常会有session-timeout
2880
分钟,即2天。
答案 2 :(得分:3)
1)这看起来像是内存泄漏吗?
是的,如果应用程序继续将对象放在地图中并且从不删除它们,那么很可能是内存泄漏。
2)每次删除调用remove(Object key,Object value)从CHM中删除a,删除的对象是否得到GC?
如果没有对它们有引用的实时(运行)线程,则只能对对象进行垃圾回收。地图只是有一个对象引用的地方。可能仍有其他地方引用同一个对象。但是将对象保留在地图中会阻止它被垃圾收集。
3)这与GC设置有关吗?
没有;如果引用了一个对象,则不能进行垃圾回收;你如何调整垃圾收集器并不重要。
答案 3 :(得分:1)
当然,现在回答为时已晚,但仅限于通过搜索找到此问题的其他人。它可能很有用。
这两个链接非常有用
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%)字节。
答案 4 :(得分:0)
如果您遇到此异常且使用的是Spring引导版本1.4.4 RELEASE或更低版本,请设置property&#34; server.session-timeout&#34;的值。在几分钟内,而不是他们的建议(秒),以便及时清理堆上的会话。 或者您可以使用EmbeddedServletContainerCustomizer的bean,但提供的值将在几分钟内设置。
示例(10分钟内的会话超时): server.session-timeout = 10(在属性文件中设置) container.setSessionTimeout(10,TimeUnit.SECONDS); (在EmbeddedServletContainerCustomizer中设置)