26

我们的 Java EE 应用程序已经开始出现一些严重的问题。具体来说,应用程序在启动后几分钟内运行了高达 99% 的老年代堆。没有抛出 OOM,但实际上 JVM 没有响应。jstat 显示老年代的大小根本没有减少,没有垃圾收集正在进行,并且 kill -3 说:

Heap
 PSYoungGen      total 682688K, used 506415K [0xc1840000, 0xf3840000, 0xf3840000)
  eden space 546176K, 92% used [0xc1840000,0xe06cd020,0xe2da0000)
  from space 136512K, 0% used [0xe2da0000,0xe2da0000,0xeb2f0000)
  to   space 136512K, 0% used [0xeb2f0000,0xeb2f0000,0xf3840000)
 PSOldGen        total 1536000K, used 1535999K [0x63c40000, 0xc1840000, 0xc1840000)
  object space 1536000K, 99% used [0x63c40000,0xc183fff8,0xc1840000)

虚拟机选项是:

-Xmx2300m -Xms2300m -XX:NewSize=800m -XX:MaxNewSize=800m -XX:SurvivorRatio=4 -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+UseParallelGC -XX:ParallelGCThreads=4 

(我将它从拥有 2300m 堆/1800m 新一代更改为尝试解决问题)

一旦 JVM 进入“内存不足”状态(一直持续下去),我就对其进行了堆转储并在其上运行 Eclipse 内存分析器。

结果很有趣。大约 200Mb 被各种对象占用(有些比其他对象拥有更多),但其余的 1.9Gb 都无法访问(可能值得注意的是,大部分被 GSON 对象占用,但我认为不是任何事情的迹象,只说我们在服务器操作期间搅动了很多 GSON 对象)。

关于为什么 VM 有这么多无法访问的对象并且根本无法收集它们的任何解释?

虚拟机:

$ /0/bin/java -version
java version "1.6.0_37"
Java(TM) SE Runtime Environment (build 1.6.0_37-b06)
Java HotSpot(TM) Server VM (build 20.12-b01, mixed mode)

当系统到达这个停顿时,下面是详细的 GC 不断打印的内容:

922.485: [GC [1 CMS-initial-mark: 511999K(512000K)] 1952308K(2048000K), 3.9069700 secs] [Times: user=3.91 sys=0.00, real=3.91 secs] 
926.392: [CMS-concurrent-mark-start]
927.401: [Full GC 927.401: [CMS927.779: [CMS-concurrent-mark: 1.215/1.386 secs] [Times: user=5.84 sys=0.13, real=1.38 secs] (concurrent mode failure): 511999K->511999K(512000K), 9.4827600 secs] 2047999K->1957315K(2048000K), [CMS Perm : 115315K->115301K(262144K)], 9.4829860 secs] [Times: user=9.78 sys=0.01, real=9.49 secs] 
937.746: [Full GC 937.746: [CMS: 512000K->511999K(512000K), 8.8891390 secs] 2047999K->1962252K(2048000K), [CMS Perm : 115302K->115302K(262144K)], 8.8893810 secs] [Times: user=8.89 sys=0.01, real=8.89 secs] 

解决了

正如 Paul Bellora 所建议的,这是由于在太短的时间内在 JVM 中创建的对象数量过多造成的。此时调试变得相当乏味。我最终做的是,使用自定义 JVM 代理来检测类。仪器将计算方法和构造函数调用。然后检查计数。我发现一个不起眼的单个操作会创建大约 200 万个对象,并触发某些单独的方法大约 150 万次(不,没有循环)。与其他操作相比,操作本身很慢。你也可以使用任何热点分析器(比如visualVM),但是我遇到了各种各样的麻烦,所以最后我自己写了。

我仍然认为 JVM 的行为是一个谜。看起来垃圾收集器停滞不前,不再清理内存,但内存分配器希望它这样做(因此不会引发 OOM)。相反,我本以为它会清除所有无法访问的内存。但是应用程序的行为也不会好很多,因为无论如何大部分时间都会花在垃圾收集上。

我用来寻求帮助的代理可以在这里找到:https ://github.com/veselov/MethodCountAgent 。它远非一款精致的软件。

4

2 回答 2

14

关于为什么 VM 有这么多无法访问的对象并且根本无法收集它们的任何解释?

(基于我们在评论中的交流)听起来这不是传统的内存泄漏,而是一些不断向新对象发送垃圾邮件的逻辑,使得 GC 难以跟上当前架构。

例如,罪魁祸首可能是某个 API 请求被多次发出,或者像我描述的无限分页场景那样“卡在”某种错误状态。这两种情况都归结为数百万个响应 gson 对象(指向Strings(指向char[]s))被实例化,然后符合 GC 条件。

正如我所说,您应该尝试隔离问题请求,然后调试并进行测量以确定这是您的应用程序或其库之一的错误或可伸缩性问题。

于 2013-01-17T05:41:50.947 回答
2

根据您列出的统计数据,我很难相信您有 1.9G 的无法访问的数据。它看起来更像是GC Overhead Limit Reached

考虑

937.746:[全GC 937.746:[CMS:512000K-> 511999K(512000K),8.8891390秒)2047999K-> 1962252K(2048000K),[CMS PERM:115302K-> 115302K(262144K)],8.8893810秒] [次数:用户= 8.89 系统=0.01,真实=8.89 秒]

如果这是真的,那么 Full GC 会释放 85K 的数据。如果您确实有 1.9G 的无法访问代码,您会看到2047999 -> ~300000.

object space 1536000K, 99% 

暗示某些东西以某种方式被创建和存储,它逃脱了一种方法,现在可能永远存在。

我需要看到更多证据证明您有 1.9G 无法访问的数据,而不是简单地被告知。

于 2013-01-17T18:42:48.133 回答