什么导致Full GC运行?

5
我有一个运行在tomcat上的web应用程序,最大堆大小设置为8GB。
如果没有用户登录到应用程序中,非可清理内存(即垃圾收集后留下的内存)会相当低,约为1GB。在这种情况下,我看到持续增长了约4GB的内存,然后垃圾收集器运行并将内存再次降至约1GB。如果没有用户登录,则此模式将继续。
GC日志显示完整的GC需要11秒,这是相当长的时间,而与之相比,小GC只需要约1秒:
2017-02-14T15:30:44.553+0530: 591.922: [GC (Allocation Failure) [PSYoungGen: 1501051K->631966K(1833472K)] 2392189K->1523112K(3030016K), 1.5100144 secs] 
    ...[Times: user=1.49 sys=0.01, real=1.51 secs]

2017-02-14T15:31:20.335+0530: 627.705: [GC (Allocation Failure) [PSYoungGen: 1553054K->595007K(1842176K)] 2444200K->1570521K(3038720K), 1.3050284 secs] 
    ...[Times: user=1.27 sys=0.04, real=1.31 secs]

2017-02-14T15:33:33.682+0530: 761.052: [GC (Allocation Failure) [PSYoungGen: 1516095K->556800K(1842176K)] 2491609K->1596474K(3038720K), 1.6957154 secs] 
    ...[Times: user=1.67 sys=0.03, real=1.69 secs]

2017-02-14T15:33:35.378+0530: 762.748: [Full GC (Ergonomics) [PSYoungGen: 556800K->365446K(1842176K)] [ParOldGen: 1039673K->1196476K(2018304K)] 1596474K->1561923K(3860480K), [Metaspace: 70472K->70472K(1114112K)], 11.2779843 secs] 
    ...[Times: user=11.13 sys=0.09, real=11.28 secs]

2017-02-14T15:34:56.232+0530: 843.602: [GC (Allocation Failure) [PSYoungGen: 1286534K->216613K(1842176K)] 2483011K->1609875K(3860480K), 1.4938761 secs] 
    ...[Times: user=1.45 sys=0.05, real=1.50 secs]

由于在GC期间所有其他线程都被挂起,因此如果用户在Full GC期间尝试访问Web应用程序,则服务器将不会响应。 是什么触发了这次Full GC?

根据日志,小型GC事件是由于分配失败引起的,而Full GC是由于人体工学引起的。 这是什么意思?

还有大量的空闲堆空间,我想延迟Full GC的发生,直到由于小型GC没有显着的内存减少。 在这种情况下,我能阻止Full GC的发生吗?

我的VM参数如下:

export CATALINA_OPTS="$CATALINA_OPTS -Xms512m -Xmx8192m -XX:+UseConcMarkSweepGC"

1
请问您能否添加您正在使用的所有与GC相关的VM选项(例如Xms、MaxGCPauseMillis、哪些收集器等)以及服务器的物理内存。如果您想要低暂停时间,延迟完整的垃圾回收并不是正确的方法,因为虽然暂停次数会减少,但每次暂停的时间会更长。所以我认为,您的堆配置过大,不适合您想要实现的目标(而且您正在使用错误的收集算法)。 - piet.t
@piet.t,VM选项已添加到问题中。 - Lahiru Chandima
应用程序可能在代码的某处进行显式的 System.gc() 调用。 我曾看到 CMS 收集器变得有些疯狂,并在调用 System.gc() 后始终执行停止整个世界的 full GC。 您可以通过 -XX:+DisableExplicitGC JVM 选项使 GC 忽略此类调用。 - Mick Mnemonic
@MickMnemonic,我的应用程序没有任何Sysgem.gc()调用,但我不确定tomcat。无论如何,如果我通过jconsole手动进行GC,原因会显示为GC日志中的System.gc()。在正常操作中,GC日志没有任何这样的条目。 - Lahiru Chandima
1
如果您的应用程序正在执行RMI/RPC,则至少JDK将执行显式的System.gc()调用。尝试上述选项或更安全的变体可能值得一试,该变体不会忽略调用,而是调用CMS收集器而不是完整GC:-XX:+ExplicitGCInvokesConcurrent - Mick Mnemonic
2个回答

2
我将指向Parallel Collector链接。 "ergonomics"是一种自动调整收集器与应用程序特定行为的方法。
大多数情况下,自动调整是好的。在您的情况下,似乎会导致GC时间过长。您可以通过调整收集器的参数来修复它。
引用文档:
最大垃圾回收暂停时间
引号 最大暂停时间目标是使用命令行选项-XX:MaxGCPauseMillis =指定的。这被解释为希望暂停时间小于或等于毫秒;默认情况下,没有最大暂停时间目标。如果指定了暂停时间目标,则会调整与垃圾回收相关的堆大小和其他参数,以尝试保持垃圾回收暂停时间短于指定值。这些调整可能会导致垃圾收集器减少应用程序的总吞吐量,并且不能保证始终达到所需的暂停时间目标。
吞吐量
引号 吞吐量目标是根据花费在垃圾回收上的时间与花费在垃圾回收之外的时间(称为应用程序时间)的比率来衡量的。该目标由命令行选项-XX:GCTimeRatio =设置,该选项将垃圾回收时间与应用程序时间的比率设置为1 /(1 + )。例如,-XX:GCTimeRatio = 19将目标设置为1/20或5%的垃圾回收总时间。默认值为99,导致目标为垃圾回收总时间的1%。
足迹
引号 使用选项-Xmx指定最大堆占用空间。此外,收集器有一个隐含目标,即在满足其他目标的情况下尽可能减小堆的大小。

-XX:MaxGCPauseMillis 看起来对我来说是一个不错的选择,但它能否将完整 GC 所需的时间最小化呢?在我的情况下,小型 GC 运行得非常快,但完整 GC 需要的时间大约是小型 GC 的 10 倍。我怀疑 -XX:MaxGCPauseMillis 能否将其降至约 1 秒左右。 - Lahiru Chandima
@LahiruChandima 当然,它会尽力尊重设置的时间,但这只是一个提示。如果在自定义GC后仍然遇到问题,您应该调查为什么有这么多需要垃圾回收的对象,以及为什么不能在更短的时间内完成。 - alain.janinm
我会添加-XX:MaxGCPauseMillis并进行检查。实际上,我认为完整GC的更长持续时间并不是由于对象数量过多造成的。通过jconsole观察,当发生小型GC时,它可以在不到2秒的时间内清除约3GB的垃圾对象。 - Lahiru Chandima
有趣,调整配置后请告诉我结果 ;) - alain.janinm
@LahiruChandima 注意,次要GC通常使用复制收集器。这个收集器的执行时间仅取决于活动对象的数量,垃圾的数量是无关紧要的。 - piet.t

0

针对您的主要问题(由于垃圾回收所花费的时间过长导致服务器停顿),这可能是由于错误的垃圾回收器使用。垃圾回收调优文档在这里。使用Garbage-First collector,您可以设置任何您认为可接受的暂停时间(默认值为200毫秒,对于服务器来说不应该是一个大问题)。


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