G1垃圾收集器:Perm Gen无限期地填满,直到执行完整GC

时间:2013-11-28 20:31:44

标签: java garbage-collection jboss7.x g1gc

我们在JBoss 7应用服务器上运行了一个相当大的应用程序。在过去,我们使用的是ParallelGC,但它在一些堆大(5 GB或更多)并且通常几乎填满的服务器中给我们带来麻烦,我们会经常得到非常长的GC暂停。

最近,我们对应用程序的内存使用情况进行了改进,并在少数情况下为应用程序运行的某些服务器添加了更多RAM,但我们也开始切换到G1,希望减少这些暂停和/或短。事情似乎有所改善,但我们看到一种奇怪的行为,以前没有发生过(使用ParallelGC):Perm Gen似乎很快就会填满,一旦达到最大值就会触发Full GC,这通常会导致长时间停顿在应用程序线程中(在某些情况下,超过1分钟)。

我们使用512 MB的最大烫发尺寸几个月,在我们的分析中,使用ParallelGC,烫发尺寸通常会停止在390 MB左右。然而,在我们切换到G1之后,上面的行为开始发生了。我尝试将最大烫发大小增加到1 GB甚至1.5 GB,但仍然会发生Full GCs(它们的频率较低)。

this link中,您可以看到我们正在使用的分析工具的一些屏幕截图(YourKit Java Profiler)。请注意,当触发Full GC时,Eden和Old Gen有很多可用空间,但Perm大小最大。在Full GC之后,Perm大小和加载类的数量急剧减少,但它们再次开始上升并重复循环。代码缓存很好,永远不会超过38 MB(在这种情况下它是35 MB)。

以下是GC日志的一部分:

  

2013-11-28T11:15:57.774-0300:64445.415:[Full GC 2126M-> 670M(5120M),23.6325510 secs]      [伊甸园:4096.0K(234.0M) - > 0.0B(256.0M)幸存者:22.0M-> 0.0B堆:2126.1M(5120.0M) - > 670.6M(5120.0M)]    [时间:用户= 10.16 sys = 0.59,真实= 23.64秒]

您可以看到完整的日志here(从我们启动服务器的那一刻开始,到完整的GC后几分钟)。

以下是一些环境信息:

  

java版“1.7.0_45”

     

Java(TM)SE运行时环境(版本1.7.0_45-b18)

     

Java HotSpot(TM)64位服务器VM(内置24.45-b08,混合模式)

启动选项:-Xms5g -Xmx5g -Xss256k -XX:PermSize=1500M -XX:MaxPermSize=1500M -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy -Xloggc:gc.log

所以这是我的问题:

  • 这是G1的预期行为吗?我在网上发现了另一篇帖子,其中有人质疑非常相似的内容,并说G1应该在Perm Gen上执行增量收集,但没有答案......

  • 我的启动参数中有什么可以改进/纠正的吗?服务器有8 GB的RAM,但似乎我们缺乏硬件,应用程序的性能在触发完整的GC之前是正常的,当用户遇到大的滞后并开始抱怨时。

5 个答案:

答案 0 :(得分:31)

Perm Gen增长的原因

  • 很多类,特别是JSP。
  • 很多静态变量。
  • 有一个类加载器泄漏。

对于那些不知道的人,这里有一个简单的方法来思考PremGen如何填满。 Young Gen没有足够的时间让事情过期,因此他们被提升到Old Gen空间。 Perm Gen拥有Young和Old Gen中对象的类。当收集Young或Old Gen中的对象并且不再引用该类时,它将从Perm Gen中“卸载”。如果Young和Old Gen没有得到GC,那么Perm Gen也没有,一旦它填满它需要一个完全停止世界的GC。有关详细信息,请参阅Presenting the Permanent Generation


切换到CMS

我知道您正在使用G1,但如果您切换到并发标记扫描(CMS)低暂停收集器-XX:+UseConcMarkSweepGC,请尝试通过添加-XX:+CMSClassUnloadingEnabled来启用类卸载和永久生成集合。


The Hidden Gotcha'

如果您使用JBoss,则RMI / DGC将gcInterval设置为1分钟。 RMI子系统每分钟强制执行一次完整的垃圾回收。这反过来又促使促销,而不是让它被收集在年轻一代。

如果不是24小时,您应该将此更改为至少1小时,以便GC进行适当的收集。

-Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000

每个JVM选项的列表

要查看所有选项,请从cmd行运行。

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version

如果您想查看JBoss正在使用的内容,则需要将以下内容添加到standalone.xml。您将获得每个JVM选项及其设置的列表。注意:它必须位于您要查看的JVM中才能使用它。如果你在外部运行它,你将看不到运行JBoss的JVM中发生了什么。

set "JAVA_OPTS= -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal %JAVA_OPTS%"

当我们只对修改后的标志感兴趣时,可以使用快捷方式。

-XX:+PrintcommandLineFlags

<强>诊断

使用jmap确定哪些类正在消耗永久生成空间。输出将显示

  • 类加载器
  • 班级数
  • 字节
  • 父装载程序
  • 活/死
  • 总计

    jmap -permstat JBOSS_PID  >& permstat.out
    

<强> JVM Options

这些设置对我有用,但取决于您的系统设置以及您的应用程序正在做什么将确定它们是否适合您。

  • -XX:SurvivorRatio=8 - 将幸存者空间比率设置为1:8,从而产生更大的幸存者空间(比率越小,空间越大)。与一个幸存者空间相比,SurvivorRatio是伊甸园空间的大小。较大的幸存者空间允许短寿命物体在年轻一代死亡的时间更长。

  • -XX:TargetSurvivorRatio=90 - 允许90%的幸存者空间被占用而不是默认的50%,从而可以更好地利用幸存者空间记忆。

  • -XX:MaxTenuringThreshold=31 - 防止从年轻人到老一代的过早晋升。允许短寿命物体在年轻一代死亡的时间更长(因此,避免晋升)。此设置的结果是由于要复制的其他对象,次要GC时间会增加。可能需要调整此值和幸存者空间大小,以便平衡幸存者空间与将长期生存的终身对象之间的复制开销。 CMS的默认设置是SurvivorRatio = 1024和MaxTenuringThreshold = 0,这会导致清除所有幸存者。这会对收集终身代的单个并发线程施加很大压力。注意:与-XX:+ UseBiasedLocking一起使用时,此设置应为15。

  • -XX:NewSize=768m - 允许指定初始年轻一代

  • -XX:MaxNewSize=768m - 允许指定最大年轻一代

这是一个更广泛的JVM options列表。

答案 1 :(得分:2)

  

这是G1的预期行为吗?

我觉得这并不令人惊讶。基本的假设是,放入permgen 的东西几乎从不变成垃圾。所以你期望permgen GC将是“最后的手段”;即JVM只有在强制进入完整GC时才会执行的操作。 (好吧,这个论点远远没有证据......但它与以下内容一致。)

我见过很多其他收藏家都有同样行为的证据; e.g。

  

我在网上发现了另一篇帖子,其中有人质疑非常相似的内容,并说G1应该在Perm Gen上执行增量收集,但没有答案......

我想我找到了同样的帖子。但有人认为应该可能是不可能的。

  

我的启动参数中有什么可以改进/纠正的吗?

我对此表示怀疑。我的理解是,这是permgen GC战略所固有的。

我建议您首先跟踪并修复使用这么多permgen的内容...或者切换到不再存在permgen堆的Java 8:请参阅PermGen elimination in JDK 8

虽然permgen泄漏是一种可能的解释,但还有其他的; e.g。

  • 过度使用String.intern()
  • 正在进行大量动态类生成的应用程序代码;例如使用DynamicProxy
  • 一个巨大的代码库......虽然这不会导致permgen churn ,因为你似乎在观察。

答案 2 :(得分:1)

我会先尝试在随机尝试JVM选项之前找到PermGen变大的根本原因。

  • 您可以启用类加载日志记录(-verbose:class,-XX:+ TraceClassLoading -XX:+ TraceClassUnloading,...)并输出输出
  • 在测试环境中,您可以尝试在加载类时监视(通过JMX)(java.lang:type = ClassLoading LoadedClassCount)。这可能有助于您找出应用程序的哪个部分负责。
  • 您也可以尝试使用JVM工具列出所有类(抱歉,但我仍然主要使用jrockit,你可以使用jrcmd。希望Oracle已将这些有用的功能迁移到Hotspot ...)

总之,找出生成这么多类的内容,然后考虑如何减少/调整gc。

干杯, 笛膜

答案 3 :(得分:1)

我同意the answer above你应该真正尝试找到实际填充你的permgen的东西,并且我非常怀疑它是关于你想找到根本原因的一些类加载器泄漏。

JBoss论坛中有this thread经历了几个这样的诊断案例以及它们是如何修复的。 this answerthis article也一般性地讨论了这个问题。在那篇文章中,提到了你可以做的最简单的测试:

  

症状

     

只有在没有重新部署应用程序的情况下才会发生这种情况   重新启动应用程序服务器。 JBoss 4.0.x系列受到了影响   从这样的类加载器泄漏。结果我无法重新部署   我们的应用程序在JVM用完之前会超过两次   PermGen记忆和崩溃。

     

解决方案

     

要识别此类泄漏,请取消部署您的应用程序,然后触发   完全堆转储(确保在此之前触发GC)。然后检查是否   您可以在转储中找到任何应​​用程序对象。如果是这样,   按照他们对他们的根的引用,你会找到原因   你的类加载器泄漏。在JBoss 4.0的情况下,唯一的解决方案是   为每次重新部署重新启动。

如果你认为重新部署可能是相关的,那么这就是我首先尝试的。 This blog post是较早的一个,做同样的事情,但也讨论细节。根据发布情况,虽然你实际上并没有重新部署任何东西,但是permgen只是自己填充。在这种情况下,检查课程+其他任何添加到permgen的方法可能就是这样(正如之前的答案中已经提到的那样)。

如果这不能提供更多见解,我的下一步就是尝试plumbr tool。它们也有一种guarantee on finding the leak for you

答案 4 :(得分:-3)

您应该使用带有-verbose:gc

的java命令启动server.bat