有关 System.gc() 您所需要知道的一切

在本文中,我们将尝试解答围绕 System.gc() 调用的一些最常见问题。希望这能有所帮助。

什么是 System.gc()

System.gc() 是 Java、Android、C# 和其他流行语言所提供的 API。调用时,其会尽最大努力从内存中清除积累的未引用对象(即:垃圾)。

谁会调用 System.gc()

System.gc() 调用可由程序栈的多个部分进行:

  • 可由程序开发人员显示调用 System.gc() 方法。
  • 有时 System.gc() 可由第三方库、框架甚至是程序服务器触发。
  • 可由外部工具(如 VisualVM)通过使用 JMX 触发
  • 如果程序使用了 RMI,则 RMI 会周期性触发 System.gc()。

调用 System.gc() 的缺点是什么?

当程序调用“System.gc()”或“Runtime.getRuntime().gc()”的 API 时,将触发 stop-the-world Full GC 事件。在 stop-the-world Full GC 的过程中,整个 JVM 都会冻结(即:所有进行中的客户事务都将被暂停)。通常,这些 Full GC 需要很长时间才能完成。因此在不需要运行 GC 时,这可能会导致糟糕的用户体验和 SLA。

JVM 在后台运行着复杂的算法,计算着何时触发 GC。在调用 System.gc() 时,这些计算都会付诸东流。比如,在 JVM 触发了一次 GC 事件后的仅仅几毫秒内,您可能又会在程序中触发一次 System.gc()。因为仅从程序中您是无法知道 GC 会在何时运行的。

是否有足够好的理由来调用 System.gc() 呢?

我们还没遇到过很多从程序中调用 System.gc() 的好理由。不过这里有一个有关某大型航空公司的有趣例子。航空公司的程序需要用到 1TB 内存。程序的 Full GC 停顿需要大约 5 分钟才能完成。是的,不要震惊,就是 5 分钟(当然我们也见过 23 分钟的 GC 停顿时间案例)。为了避免停顿对客户体验造成影响,航空公司采用了一种十分聪明的解决方案。每晚,他们都会从负载均衡池中挑选一个 JVM 实例。然后通过该 JVM 上的 JMX 显式触发 System.gc()。GC 事件完成、垃圾从内存中清除后,再将 JVM 放回负载均衡池中。这确实是一个很聪明的方案,他们也得以最大限度地减少 5 分钟 GC 停顿时间对客户所造成的影响。

如何检测是否从您的程序中进行了 System.gc() 调用?

“谁会调用 System.gc()?”章节中提到过,System.gc() 会在多种源头进行调用,而不单单是通过程序的源代码调用。所以只是在程序代码中搜索“System.gc()”字符串并不足以判断程序是否进行了 System.gc() 调用。那么问题就来了:如何检测整个程序栈中是否触发了 System.gc() 调用呢?

这就是 GC 日志的实用之处了。在程序中启用 GC 日志吧。实际上我们建议在所有生产服务器中始终启用 GC 日志,因为这可以帮助您进行故障排除与程序性能优化工作。启用 GC 日志仅会增加几乎可忽略(如果有的话)的系统开销。您可将 GC 日志上传至垃圾回收日志分析工具(如:GCeasyHP JMeter 等)中。这些工具可以生成内容丰富的垃圾回收分析报告。

图:由 GCeasy.io 工具提供的 GC 原因

上图是由 GCeasy 生成的报告中的“GC 原因”章节。您可以看到,“System.gc()”被调用了 304 次,占据了 52.42% 的 GC 停顿时间。

如何移除 System.gc() 调用?

您可通过以下方式移除显式的 System.gc() 调用:

a.搜索并替换

经典永不过时 :-)。在程序代码库中搜索“System.gc()”与“Runtime.getRuntime().gc()”。如果出现匹配,就将相应的内容移除。此方案适合“System.gc()”在您的程序源代码中触发的情形。如果“System.gc()”从第三方库、框架或外部源中调用,那么此方案将不起作用。在这种情况下,您可考虑使用 #b 中所列出的选项。

b. -XX:+DisableExplicitGC

您可以强制禁用 System.gc() 调用。启动程序时传递 JVM 参数“-XX:+DisableExplicitGC”即可。该选项将停用一切来自程序栈中任意位置的“System.gc()”调用。

c. -XX:+ExplicitGCInvokesConcurrent

您可传递 JVM 参数“-XX:+ExplicitGCInvokesConcurrent”。传递此参数时,GC 回收会与程序线程并发运行,进而缩短冗长的停顿时间。

d.RMI

如果您的程序使用了 RMI,则可控制其中进行“System.gc()”调用的频率。您可在程序启动时启用以下 JVM 参数来对频率进行配置:

-Dsun.rmi.dgc.server.gcInterval=n

-Dsun.rmi.dgc.client.gcInterval=n

这些属性的默认值为:

JDK 1.4.2 和 5.0 为 60000 毫秒(即 60 秒)

JDK 6 及更高版本为 3600000 毫秒(即 60 分钟)

您可将这些属性设置尽可能大的值以最大限度地减少影响。

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: