【问题标题】:Memory Fully utilized by Java ConcurrentHashMap (under Tomcat)Java ConcurrentHashMap 充分利用的内存(在 Tomcat 下)
【发布时间】:2011-04-26 22:12:10
【问题描述】:

这是一个内存堆栈(用作缓存),仅由静态 ConcurrentHashMap (CHM) 组成。

所有传入的 HTTP 请求数据都存储在这个 ConcurrentHashMap 中。并且有一个异步调度进程,它从同一个 ConcurrentHashMap 中获取数据,并在将它们存储到数据库后删除 key.value。

该系统运行良好且流畅,但仅在以下条件下发现,内存已充分利用(2.5GB)并且所有 CPU 时间都用于执行 GC:

-1000/s 的并发 http 命中率

-在 15 分钟内保持相同的并发命中

每次写入数据库时​​,异步进程都会记录 CHM 的剩余大小。 CHM.size() 维持在 Min:300 到 Max:3500 左右

我认为此应用程序存在内存泄漏。所以我使用 Eclipse MAT 来查看堆转储。运行嫌疑人报告后,我从 MAT 获得了这些 cmets:

“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 四核,8 线程。

-4GB 内存

-Windows 2008 R2

-Tomcat 6.0.29

【问题讨论】:

  • 用 EhCache 的实例替换哈希映射有多难?这些库针对此类任务进行了优化。
  • 目前我们尽量不对现有代码进行太多更改,因为我们还没有分析影响。 EhCache 最初是考虑的一部分,但不知何故没有被选为实现选择。

标签: java


【解决方案1】:

这个问题困扰了我 7 天!最后我发现了真正的问题!以下是我尝试过但未能解决 OutOfMemory 异常的任务:

-从使用 concurrenthashmap 更改为 ehcache。 (原来ehcache也在使用ConcurrentHashMap)

-将所有硬引用更改为软引用

-根据Dr. Heinz M. Kabutz 的建议,将 AbstractMap 与 concurrnetHashMap 一起覆盖

百万美元的问题实际上是“为什么 30-45 分钟后,内存开始释放回堆池?”

真正的根本原因是因为还有其他东西仍然持有实际的变量会话,罪魁祸首是tomcat中的http会话仍然处于活动状态!因此,即使 http 会话已经完成,但如果超时设置为 30 分钟,tomcat 会在 JVM GC 之前将会话信息保留 30 分钟。将超时设置更改为 1 分钟作为测试后立即解决问题。

$tomcat_folder\conf\web.xml

<session-config>
    <session-timeout>1</session-timeout>
</session-config>

希望这将帮助任何有类似问题的人。

【讨论】:

  • 我想知道更改为持久会话管理器(使用文件存储)是否在保持会话超时的同时解决了您的问题?
  • 可能。如果我有时间重新测试这个
  • 你知道为什么会话没有关闭吗?哪些变量不断地引用会话中的对象?
  • 这很正常,HttpSession 永远不会被客户端关闭,服务器会保留它以防进一步的请求进入,直到超时到期。正如其他人所说,这从根本上是他的测试设计中的一个缺陷,其中不断创建新会话。
【解决方案2】:

我认为您使用了太多 会话数据无法立即容纳 内存 .试试这个:

  1. 编辑 bin/setenv.shJVM 参数 在您的 Tomcat 启动器上设置的任何位置:

    追加-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,在&lt;/Context&gt;之前添加:

    <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's) 应用程序通常有 session-timeout2880 分钟,即 2 天。

参考:Tomcat 7.0 Session Manager Configuration

【讨论】:

  • 您好,使用内置的spring-boot服务器如何实现呢?我应该更改什么配置?
【解决方案3】:

1) 这看起来像内存泄漏吗?

是的,如果应用程序继续将对象放入地图中并且从不删除它们,那么这很可能是内存泄漏。

2) 每次remove调用remove(Object key, Object value)从CHM中移除一个,被移除的对象是否得到GC?

只有在没有活动(运行)线程引用对象时,才能对对象进行垃圾回收。地图只是一个引用对象的地方。可能还有其他地方引用了同一个对象。但是将对象保留在地图中会阻止它被垃圾收集。

3) 这与 GC 设置有关吗?

没有;如果一个对象被引用,它不能被垃圾回收;你如何调整垃圾收集器并不重要。

【讨论】:

  • 第二个和第三个问题我必须完全同意你的看法。但是对于第一个问题,如果是内存泄漏,有没有可能在 30-45 分钟后,Java 堆内存利用率回到其初始化状态?
  • @Reusable 如果发生这种情况,则不应将其标记为真正的“内存泄漏”。但是,如果您发现内存在您认为应该回收之后没有被回收(因为您不再引用数据),那么这表明您的 Map 使用中的某些逻辑存在缺陷。
  • @matt b 我和你的想法是一样的,但这纯粹是我的粗略猜测,还有一些其他对象正在持有被删除的键/值的引用。没有任何成功,仍在检查代码。
  • 您在使用 Eclipse 吗?如果是这样,有一个出色的插件可以分析堆转储以查找内存泄漏:eclipse.org/matSee here 获取有关如何生成堆转储的信息。
  • @matt b 我已经在使用 Eclipse MAT :) 我已经用 MAT 生成的摘要和可疑报告编辑了上面的主要问题。但是,我没有看到任何类仍然保留对其 CHM 键/值的任何引用,从而阻止它进行 GC :(
【解决方案4】:

当然,现在回答为时已晚,但仅适用于通过搜索找到此问题的其他人。可能有用。

这两个链接很有用
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().length 约为 100,但 10 分钟后 qwith 负载测试软件约为 500,最后系统崩溃并出现相同的 OutOfMemory 错误,MAT 显示相同:

org.apache.catalina.loader.StandardClassLoader @ 0x853f0280" 占用 2,135,429,456 (94.76%) 字节。

【讨论】:

    【解决方案5】:

    如果您遇到此异常并且正在使用 Spring Boot 版本 1.4.4 RELEASE 或更低版本,请以分钟为单位设置属性“server.session-timeout”的值,而不是他们建议的值(秒),以便堆会及时清理。 或者您可以使用 EmbeddedServletContainerCustomizer 的 bean,但提供的值将在几分钟内设置。

    示例(会话超时 10 分钟): server.session-timeout=10(在属性文件中设置) container.setSessionTimeout(10, TimeUnit.SECONDS); (在 EmbeddedServletContainerCustomizer 中设置)

    【讨论】:

      猜你喜欢
      • 2017-03-30
      • 1970-01-01
      • 1970-01-01
      • 2016-05-13
      • 2015-08-11
      • 2012-01-11
      • 1970-01-01
      • 2010-09-22
      • 2016-04-26
      相关资源
      最近更新 更多