10

简短形式:CMS 垃圾收集器似乎无法收集越来越多的垃圾;最终,我们的 JVM 被填满,应用程序变得无响应。通过外部工具(JConsole 或jmap -histo:live)强制 GC 将其清理一次。

更新:问题似乎与 JConsole 的 JTop 插件有关;如果我们不运行 JConsole,或者在没有 JTop 插件的情况下运行它,行为就会消失。

(技术说明:我们在 Linux 2.6.9 机器上运行 Sun JDK 1.6.0_07,32 位。升级 JDK 版本并不是一个真正的选择,除非有不可避免的主要原因。此外,我们的系统不是连接到可访问 Internet 的机器,因此不能选择 JConsole 的屏幕截图等。)

我们目前正在使用以下标志运行我们的 JVM:

-server -Xms3072m -Xmx3072m -XX:NewSize=512m -XX:MaxNewSize=512m 
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled 
-XX:CMSInitiatingOccupancyFraction=70 
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 
-XX:+DisableExplicitGC

观察 JConsole 中的内存图,在我们应用程序生命周期的前几个小时内,每 15 分钟左右运行一次完整的 GC;在每次完全 GC 之后,仍然有越来越多的内存在使用中。几个小时后,系统进入稳定状态,在 CMS 老一代中大约有 2GB 的已用内存。

这听起来像是典型的内存泄漏,除了如果我们使用任何强制执行完整 GC 的工具(点击 JConsole 中的“收集垃圾”按钮,或运行jmap -histo:live等),老一代突然下降到使用约 500MB,而我们的应用程序在接下来的几个小时内再次变得响应(在此期间相同的模式继续 - 在每次完整 GC 之后,越来越多的老一代已满。)

需要注意的一点:在 JConsole 中,报告的 ConcurrentMarkSweep GC 计数将保持为 0,直到我们使用 jconsole/jmap/etc 强制进行 GC。

依次使用jmap -histojmap -histo:live,我能够确定明显未收集的对象包括:

  • 数百万HashMaps 和数组HashMap$Entry(以 1:1 的比例)
  • 几百万Vector个s和Object数组(1:1的比例,和HashMap的个数差不多)
  • 几百万个HashSet, Hashtable, 和com.sun.jmx.remote.util.OrderClassLoaders,以及数组Hashtable$Entry(每个的数量大致相同;大约是 HashMap 和向量的一半)

下面是 GC 输出的一些摘录;我对它们的解释似乎是 CMS GC 在没有故障转移到 stop-the-world GC 的情况下中止。我是否以某种方式误解了这个输出?有什么会导致这种情况吗?

在正常运行时,CMS GC 输出块如下所示:

36301.827: [GC [1 CMS-initial-mark: 1856321K(2621330K)] 1879456K(3093312K), 1.7634200 secs] [Times: user=0.17 sys=0.00, real=0.18 secs]
36303.638: [CMS-concurrent-mark-start]
36314.903: [CMS-concurrent-mark: 7.804/11.264 secs] [Times: user=2.13 sys=0.06, real=1.13 secs]
36314.903: [CMS-concurrent-preclean-start]
36314.963: [CMS-concurrent-preclean: 0.037/0.060 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
36314.963: [CMS-concurrent-abortable-preclean-start]
36315.195: [GC 36315.195: [ParNew: 428092K->40832K(471872K), 1.1705760 secs] 2284414K->1897153K(3093312K), 1.1710560 secs] [Times: user=0.13 sys=0.02, real=0.12 secs]
CMS: abort preclean due to time 36320.059: [CMS-concurrent-abortable-preclean: 0.844/5.095 secs] [Times: user=0.74 sys=0.05, real=0.51 secs]
36320.062: [GC[YG occupancy: 146166 K (471872 K)]36320.062: [Rescan (parallel), 1.54078550 secs]36321.603: [weak refs processing, 0.0042640 secs] [1 CMS-remark: 1856321K(2621440K)] 2002488K(3093312K), 1.5456150 secs] [Times: user=0.18 sys=0.03, real=0.15 secs]
36321.608: [CMS-concurrent-sweep-start]
36324.650: [CMS-concurrent-sweep: 2.686/3.042 secs] [Times: uesr=0.66 sys=0.02, real=0.30 secs]
36324.651: [CMS-concurrent-reset-start]
36324.700: [CMS-concurrent-reset: 0.050/0.050 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

就是这样;下一行将是下一个 ParNew GC。

当我们使用 jmap -histo:live 强制 GC 时,我们会得到:

48004.088: [CMS-concurrent-mark: 8.012/8.647 secs] [Times: user=1.15 sys=0.02, real=0.87 secs]
(concurrent mode interrupted)

然后是下面表格的约 125 行:(一些 GeneratedMethodAccessor、一些 GeneratedSerializationConstructorAccessor、一些 GeneratedConstructorAccessor 等)

[Unloading class sun.reflect.GeneratedMethodAccessor3]

其次是:

: 1911295K->562232K(2621440K), 15.6886180 secs] 2366440K->562232K(3093312K), [CMS Perm: 52729K->51864K(65536K)], 15.6892270 secs] [Times: user=1.55 sys=0.01, real=1.57 secs]

提前致谢!

4

4 回答 4

7

com.sun.jmx.remote.util.OrderClassLoader 用于 JMX 的远程层,快速查看代码表明它们是作为 JVM 内部远程请求的解组过程的一部分而创建的。这些类加载器的生命周期将与被解组的事物的生命周期直接相关,这样一旦不再有对该事物的任何引用,就可以释放类加载器。

如果在这种情况下这些实例的存在是您使用 JConsole 检查 JVM 中发生的事情的直接结果,我不会感到惊讶。看起来它们只是作为正常操作的一部分被 GC 清理。

我猜想 JMX 实现中可能存在错误(在相对最新的 JVM 中似乎不太可能),或者您可能有一些自定义 MBean 或正在使用一些导致问题的自定义 JMX 工具。但最终,我怀疑 OrderClassLoader 可能是一个红鲱鱼,问题出在其他地方(损坏的 GC 或其他一些泄漏)。

于 2010-10-06T18:01:41.493 回答
5

技术说明:我们在 Linux 2.6.9 机器上运行 Sun JDK 1.6.0_07,32 位。升级 JDK 版本并不是一个真正的选择,除非有不可避免的主要原因。

几个较新的 Java 版本对 CMS 垃圾收集器进行了更新。尤其是 6u12、6u14 和 6u18。

我不是 GC 方面的专家,但我猜 6u14 中的 preclean修复可能会解决您遇到的问题。当然,对于 6u18 的类卸载 bug,我也可以这么说。就像我说的,我不是 GC 方面的专家。

有以下修复:

  • 6u10:(影响 6u4+)CMS 在 -XX:+ParallelRefProcEnabled 时从不清除引用
  • 6u12:CMS:并发预清理期间溢出对象数组的编码不正确
  • 6u12:CMS:使用并行并发标记时溢出处理不正确
  • 6u14:CMS:断言失败“is_cms_thread == Thread::current()->is_ConcurrentGC_thread()”
  • 6u14:CMS:需要 CMSInitiatingPermOccupancyFraction 进行烫发,与 CMSInitiatingOccupancyFraction 分离
  • 6u14:CMS 断言:_concurrent_iteration_safe_limit 更新丢失
  • 6u14:CMS:预清理参考列表期间溢出处理不正确
  • 6u14:使用 CMS 和 COOP 运行时,SIGSEGV 或 (!is_null(v),"oop value can never be zero") 断言
  • 6u14:CMS:CompactibleFreeListSpace::block_size() 中的活锁。
  • 6u14:使 CMS 与压缩的 oops 一起工作
  • 6u18: CMS: 核心转储 -XX:+UseCompressedOops
  • 6u18:CMS:与类卸载相关的错误
  • 6u18:CMS:在存在 cms 预清洁的情况下,ReduceInitialCardMarks 不安全
  • 6u18:[回归] -XX:NewRatio 和 -XX:+UseConcMarkSweepGC 导致致命错误
  • 6u20:卡标记可以延迟太久

除了以上所有内容,6u14 还引入了G1垃圾收集器,尽管它仍处于测试阶段。G1 旨在取代 Java 7 中的 CMS。

G1 可用于 Java 6u14 和更新版本,带有以下命令行开关:

-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC

于 2010-10-06T15:17:41.843 回答
-1

我将从更简单的事情开始,例如:

-server -Xms3072m -Xmx3072m -XX:+UseParallelOldGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 

看看这是否满足您的需求。

于 2010-10-07T03:44:07.863 回答
-2

看起来您正在构建指向其所有者的对象( A 指向 B 指向 A )。这导致引用计数保持大于零,因此垃圾收集器无法清理它们。当你释放它们时,你需要打破循环。取消 A 或 B 中的引用将解决问题。这甚至在更大的参考中也有效(A -> B -> C -> D -> A)。您的 HashMap 可以使用向量和对象数组。

远程加载器的存在可能表明无法清理和关闭对通过 JNDI 或其他远程访问方法加载的对象的引用。

编辑:我再次看了你的最后一行。您可能希望增加 perm 分配。

于 2010-10-06T14:52:05.487 回答