Java Web应用程序中的内存泄漏

我有一个在Tomcat 7上运行的Java Web应用程序似乎有内存泄漏。 在负载下(使用JConsole确定),应用程序的平均内存使用量随时间线性增加。 在内存使用率达到稳定水平后,性能会显着下降。 响应时间从~100ms到[300ms,2500ms],因此这实际上导致了实际问题。

我的应用程序的JConsole内存配置文件: 应用程序内存配置

使用VisualVM,我看到字符数组(即char [])使用的内存至少有一半,并且字符串的大多数(大致相同数量,300,000个实例)是以下之一:“分配失败” ,“复制”,“次要GC的结束”,所有这些似乎都与垃圾收集通知有关。 据我所知,该应用程序根本不监视垃圾收集器。 VisualVM找不到任何这些字符串的GC根,所以我很难跟踪它。

内存分析器堆转储: 堆转储,无法访问的内存

我无法解释为什么内存使用情况会如此,但我有一个关于为什么性能一旦降低就会降级的理论。 如果内存碎片化,应用程序可能需要很长时间才能分配连续的内存块来处理新请求。

将其与内置的Tomcat服务器状态应用程序进行比较,内存会增加并保持稳定,但不会像我的应用程序那样达到很高的“底限”。 它也没有大量无法访问的char []。

Tomcat服务器状态应用程序的JConsole内存配置文件: 在此处输入图像描述

Tomcat服务器状态applicationp的内存分析器堆转储: 在此处输入图像描述

这些字符串可以在哪里分配,为什么它们不被垃圾收集? 是否存在可能影响此问题的Tomcat或Java设置? 是否有特定的包可能会影响这个?

我从tomcat\bin\setenv.bat删除了以下JMX配置:

 set "JAVA_OPTS=%JAVA_OPTS% -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false" 

我不能再获得详细的内存堆转储,但内存配置文件看起来要好得多: 在此处输入图像描述

24小时后,内存配置文件看起来一样: 在此处输入图像描述

我建议使用memoryAnalyzer来分析你的堆,它提供了更多的信息。
http://www.eclipse.org/mat/有一个独立的应用程序和eclipse嵌入式应用程序。 你只需要在你的应用程序上运行jmap并用它来分析结果。

我可以推荐jvisualvm ,它随每个Java安装一起提供。 启动程序,连接到Web应用程序。 转到监视器 – >堆转储 。 现在可能需要一些时间(取决于大小)。 通过堆转储导航很容易,但你必须弄清楚自己的意思(虽然不是太复杂),例如

转到Classes (在heapdump中),选择java.lang.String ,右键单击Instances View中的Show 。 之后,您将在左侧的表中看到系统中当前处于活动状态的字符串实例。 点击一个String实例,你会在右表的右上方看到一些字符串优先,就像String一样。

在右表的右下角,您将看到引用 String实例的位置。 在这里,您必须检查引用大多数* String *的位置。 但是根据你的情况(176/210,很好找到一些很快就会导致问题的字符串示例),在一些检查后应该清楚问题所在。

平台是由可用内存降至默认百分比阈值以下导致Full GC引起的。 这解释了为什么性能在JVM尝试查找和释放内存时不断暂停时会下降。

我通常会建议查看对象缓存,但在您的情况下,我认为您的堆大小对于Tomcat实例+ webapp来说太低了。 我建议将堆增加到1G( -Xms1024m -Xmx1024m ),然后再次检查内存使用情况。

如果你仍然看到相同的行为,那么你应该采取另一个堆转储并查看String和Char之后的最大消费者。 根据我的经验,这通常是缓存机制。 如果可能的话,要么进一步增加内存,要么减少缓存存储。 有些缓存只定义了对象的数量,因此您需要了解每个缓存对象的大小。

一旦你了解你的内存使用情况,你可以再次降低它,但恕我直言512MB将是最低限度。

更新:

您无需担心无法访问的对象,因为它们应该由GC清理。 此外,按类型划分的最大消费者是String和Char是正常的 – 大多数对象都包含某种字符串,因此字符串和字符在频率上是最常见的。 了解包含字符串的对象的内容是查找内存使用者的关键。

我刚刚在一个完全不同的应用程序中遇到了同样的问题,所以tomcat7可能不应该受到指责。 Memory Analyzer在进程中显示10M无法访问的String实例(已运行约2个月),并且大多数/所有实例都具有与垃圾收集相关的值(例如,“分配失败”,“次要GC结束”)

内存分析器

内存分析器

Full GC现在每2秒运行一次,但这些字符串不会被收集。 我的猜测是我们遇到了GC代码中的错误。 我们使用以下java版本:

 $ java -version java version "1.7.0_06" Java(TM) SE Runtime Environment (build 1.7.0_06-b24) Java HotSpot(TM) 64-Bit Server VM (build 23.2-b09, mixed mode) 

以及以下VM参数:

 -Xms256m -Xmx768m -server -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:NewSize=32m -XX:MaxNewSize=64m -XX:SurvivorRatio=8 -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/path/to/file 

不小心,我在Tomcat的conf / catalina.properties文件中偶然发现了以下几行,这些文件激活了字符串缓存。 如果您打开了任何一个,这可能与您的情况有关。 似乎其他人警告使用该function。

 tomcat.util.buf.StringCache.byte.enabled=true #tomcat.util.buf.StringCache.char.enabled=true #tomcat.util.buf.StringCache.trainThreshold=500000 #tomcat.util.buf.StringCache.cacheSize=5000 

由于这听起来不太具体,因此一名候选人将成为JSF。 但后来我也预料到哈希映射会泄漏。

你应该使用JSF:在web.xml中你可以尝试:

  • javax.faces.STATE_SAVING_METHOD 客户端
  • com.sun.faces.numberOfViewsInSession 0
  • com.sun.faces.numberOfLogicalViews 1

至于工具: JavaMelody可能对持续统计很有意义,但需要付出努力。

尝试使用MAT并确保在解析heapdump时,不要删除无法访问的对象。

为此,请按照此处的教程进行操作。

然后你可以运行一个简单的Mem Leak Analysis( 这是一个很好的教程)

这应该很快引导你找到根本原因。