JVM标志CMSClassUnloadingEnabled实际上是做什么的?

187

我怎么也找不到Java VM标志CMSClassUnloadingEnabled的定义,除了一些非常模糊的高级定义,比如“解决PermGen问题”(它并没有, 顺便说一句)。

我已经在Sun/Oracle的网站上查找过,甚至选项列表也没有明确说明它的作用。

根据该标志的名称,我猜测CMS垃圾收集器默认情况下不会卸载类,而这个标志则可以开启该功能,但我不能确定。

3个回答

225
更新:该答案适用于Java 5-7,Java 8已经解决了这个问题:https://blogs.oracle.com/poonam/about-g1-garbage-collector,-permanent-generation-and-metaspace 感谢mt.uulu 对于Java 5-7:
标准的Oracle/Sun VM看待世界的方式是:类是永久存在的。所以一旦加载,即使没有任何人再使用它们,它们也会留在内存中。通常这不是问题,因为您没有那么多纯粹的“设置”类(即仅用于设置,然后永远不再使用的类)。即使它们占用了1MB,谁在乎呢。
但是最近,我们有像Groovy这样的语言,它在运行时定义类。每次运行脚本时,都会创建一个(或多个)新类,并且它们会永久留在PermGen中。如果您正在运行服务器,那么这意味着您有一个内存泄漏。
如果启用CMSClassUnloadingEnabled,GC也将清除PermGen并删除不再使用的类。
[编辑] 您还需要启用UseConcMarkSweepGC(感谢Sam Hasler)。请参见此答案:https://dev59.com/jXA65IYBdhLWcg3wogEb#3720052

17
根据https://dev59.com/jXA65IYBdhLWcg3wogEb#3720052的说法,如果要使`CMSClassUnloadingEnabled`发挥作用,还必须设置`UseConcMarkSweepGC`。 - Sam Hasler
1
不确定这会如何影响使用UseConcatSweepGC的想法,但最近在CMSClassUnloadingEnabled中发现了一个错误,已经被修复。在这里标记为已修复:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8000325 - Bill Rosmus
3
@Kevin:是的,肯定的。请看http://groovy.codehaus.org/Running页面的底部:“Groovy会动态地创建类,但默认的Java虚拟机不会回收永久代(PermGen)中的类。如果你正在使用Java 6或更高版本,请添加-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC。需要启用CMSClassUnloadingEnabled,才能使用UseConcMarkSweepGC。” - Aaron Digulla
1
一篇关于如何同时使用UseConcMarkSweepGC和CMSClassUnloadingEnabled的好文章。http://blog.redfin.com/devblog/2012/06/cmsclassunloadingenabled-at-redfin.html#.VGu0efnF-So - Victor
1
不再适用于1.8:https://blogs.oracle.com/poonam/about-g1-garbage-collector,-permanent-generation-and-metaspace - mtuulu
显示剩余3条评论

36
根据Java JVM的-XX选项的最全列表博客文章,它确定在CMS垃圾收集器下是否启用类卸载。默认值为false。还有另一个名为ClassUnloading的选项,默认值为true,这可能会影响其他垃圾收集器。
如果GC检测到以前加载的类在JVM中不再被使用,那么它可以回收用于保存类的字节码和/或本机代码的内存。
设置CMSClassUnloadingEnabled 可能有助于解决您的PermGen问题,如果您目前正在使用CMS收集器。但是,您很可能没有使用CMS,或者存在真正的与类加载器相关的内存泄漏。在后一种情况下,您的类将永远不会出现在GC中未使用...因此永远不会被卸载。
Aaron Digulla说“类是永久的”。即使在纯Java世界中,这也不完全正确。实际上,类的生命周期与其类加载器相关联。因此,如果您可以安排类加载器进行垃圾收集(这并不总是容易的事情),则它所加载的类也将被垃圾收集。
实际上,当您重新部署Web应用程序时就会发生这种情况。 (或者至少,如果您可以避免导致PermGen存储泄漏的问题,那么就应该是这样。)

23

这种情况下会很有用的一个示例:

在我们的Weblogic 10.3 JVM上设置-XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled 帮助解决了一个问题,即JAX-WS实现为每个Web服务调用创建一个新的代理类,最终导致内存不足错误。

它并不容易追踪。以下代码始终为port返回相同的代理类。

final MyPortType port = 
Service.create(
        getClass().getResource("/path/to.wsdl"), 
        new QName("http://www.example.com", "MyService"))
    .getPort(
        new QName("http://www.example.com", "MyPortType"), 
        MyPortType.class);

在内部,这个代理委托给一个weblogic.wsee.jaxws.spi.ClientInstance实例,该实例再次委托给一个新的$Proxy[nnnn]类,其中n在每次调用时递增。添加标志时,n仍然递增,但至少这些临时类从内存中删除了。

更一般地说,当通过java.lang.reflect.Proxy大量使用Java反射和代理时,这可能非常有用。


分享真实经验加1。我们在Torquebox上也遇到了这个问题,由于JRuby编译过程,服务器生成了大量的类。 - nurettin
7
请注意,-XX:+CMSPermGenSweepingEnabled已被弃用,应使用-XX:+CMSClassUnloadingEnabled - nurettin
4
解决这个问题的真正方法是创建端口一次并重复使用它。这就是JAX-WS的正确使用方式。此外,该端口也是100%线程安全的。 - rustyx
1
@rukavitsya:就像我在答案中所说的那样,每次调用上述逻辑时,都会创建一个新的代理。 - Lukas Eder
1
@rukavitsya:嘿,抱歉,我不知道。那是在2012年...现在我在做其他项目。 - Lukas Eder
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接