Java ConcurrentMarkSweep垃圾收集器不会删除所有垃圾

时间:2010-10-06 14:36:09

标签: java garbage-collection concurrent-mark-sweep

简短形式: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中观察内存图,有一个完整的GC,在我们的应用程序生命周期的前几个小时内每隔约15分钟运行一次;在每个完整的GC之后,仍然有越来越多的内存在使用中。几个小时后,系统达到稳定状态,CMS旧版本中有大约2GB的已用内存。

这听起来像是经典的内存泄漏,除非我们使用任何强制完整GC的工具(点击JConsole中的“收集垃圾”按钮,或运行jmap -histo:live等),旧版本会突然掉落使用~500MB,我们的应用程序在接下来的几个小时内再次响应(在此期间相同的模式继续 - 在每个完整的GC之后,越来越多的旧版本已满。)

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

依次使用jmap -histojmap -histo:live,我可以确定明显未收集的对象包含:

  • 数百万HashMap s和HashMap$Entry数组(比例为1:1)
  • 数百万Vector s和对象数组(比例为1:1,与HashMaps的数量大致相同)
  • 数百万HashSetHashtablecom.sun.jmx.remote.util.OrderClassLoader以及Hashtable$Entry的数组(大约相同数量;大约是HashMaps的一半)和矢量)

下面的GC输出中有一些摘录;我对它们的解释似乎是CMS GC中止而没有故障转移到世界各地的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 个答案:

答案 0 :(得分:7)

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

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

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

答案 1 :(得分:5)

  

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

几个较新的Java版本已经对CMS垃圾收集器进行了更新。特别是6u12,6u14和6u18。

我不是GC的专家,但我猜测6u14 中的预清理修复可以修复你看到的问题。当然,我可以对6u18的类卸载错误说同样的话。就像我说的那样,我不是GC的专家。

有以下修复:

  • 6u10 :(影响6u4 +)当-XX:+ ParallelRefProcEnabled
  • 时,CMS永远不会清除所指对象
  • 6u12:CMS:并发预清理期间溢出对象数组的编码不正确
  • 6u12:CMS:使用并行并发标记时出现错误的溢出处理
  • 6u14:CMS:断言失败“is_cms_thread == Thread :: current() - > is_ConcurrentGC_thread()”
  • 6u14:CMS:需要CMSInitiatingPermOccupancyFraction for perm,与CMSInitiatingOccupancyFraction离婚
  • 6u14:CMS断言:_concurrent_iteration_safe_limit更新错过
  • 6u14:CMS:预清理参考列表期间的溢出处理不正确
  • 6u14:SIGSEGV或(!is_null(v),“oop值永远不能为零”)与CMS和COOP一起运行时的断言
  • 6u14:CMS:CompactibleFreeListSpace :: block_size()中的Livelock。
  • 6u14:使CMS使用压缩的oops
  • 6u18:CMS:带-XX的核心转储:+ UseCompressedOops
  • 6u18:CMS:与类卸载相关的错误
  • 6u18:CMS:在存在cms预扫描时ReduceInitialCardMarks不安全
  • 6u18:[回归] -XX:带-XX的NewRatio:+ UseConcMarkSweepGC导致致命错误
  • 6u20:卡片标记可以延迟太久

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

G1可以在Java 6u14及更高版本中使用以下命令行开关:

-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC

答案 2 :(得分:-1)

我会从更简单的事情开始,比如:

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

看看这是否符合您的需求。

答案 3 :(得分:-2)

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

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

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