21

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

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

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

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

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

将其与内置的 Tomcat 服务器状态应用程序进行比较,内存会增加并趋于平稳,但不会像我的应用程序那样达到很高的“地板”。它也没有大量不可访问的 char[]。

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

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

这些字符串可以分配到哪里,为什么它们没有被垃圾收集?是否有可能影响此的 Tomcat 或 Java 设置?是否有特定的软件包可能会影响这一点?

4

8 回答 8

7

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

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 小时后,内存配置文件看起来一样: 在此处输入图像描述

于 2012-12-04T19:09:55.187 回答
3

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

于 2012-11-21T11:41:11.077 回答
3

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

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

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

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

更新:

您不必担心无法访问的对象,因为它们应该由 GC 清理。此外,按类型划分的最大消费者通常是 String 和 Char - 大多数对象将包含某种 String,因此 Strings 和 Chars 在频率上是最常见的,这是有道理的。了解包含字符串的对象是什么,是找到内存使用者的关键。

于 2012-11-26T17:57:59.343 回答
2

我可以推荐每个 Java 安装附带的jvisualvm 。启动程序,连接到您的 Web 应用程序。转到Monitor -> Heap Dump。现在可能需要一些时间(取决于大小)。通过 Heap Dump 导航非常简单,但是您必须自己弄清楚含义(虽然不太复杂),例如

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

在右表的右下角,您将看到 String实例的引用位置。在这里,您必须检查您的大部分 *String* 是从哪里引用的。但是对于您的案例(176/210,很有可能找到一些很快导致您的问题的字符串示例),经过一些检查后应该清楚问题出在哪里。

于 2012-11-26T17:06:25.633 回答
2

我只是在一个完全不同的应用程序中遇到了同样的问题,所以 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)

以及以下虚拟机参数:

-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
于 2012-12-04T11:36:18.503 回答
1

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

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
于 2013-09-26T11:37:48.497 回答
1

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

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

然后你可以运行一个简单的内存泄漏分析(是一个很好的教程)

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

于 2013-10-11T20:37:49.827 回答
0

因为这听起来不具体,所以一个候选人应该是 JSF。但是我本来预计哈希映射也会泄漏。

如果您使用 JSF:在web.xml中,您可以尝试:

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

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

于 2012-11-26T16:58:00.740 回答