Java 堆被无法访问的对象淹没
我们的 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)
VM 选项包括:
-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 Memory Analyzer。
结果非常有趣。大约200Mb被各种对象占用(有些对象比其他对象拥有更多),但其余的1.9Gb都是无法访问的(值得注意的是,大多数被GSON对象占用,但我不认为这表示什么,只是说我们在服务器操作期间搅动了很多GSON对象)。
关于为什么VM有这么多无法访问的对象,并且根本无法收集它们的任何解释?
JVM:
$ /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。它远非一个抛光的软件。