翻译 | 关于 System.gc() 你需要知道的一切

2019-09-152443

原文自国外技术社区dzone,作者为 Ram Lakshmanan,传送门

糟糕的代码设计是无用的垃圾

在这篇文章中,我们会围绕 System.gc() 来探讨几个最常见的问题。我们希望这个可以给你提供帮助。

什么是 System.gc()?

System.gc() 是在 Java、Android、C# 和其他流行的语言中都有提供的一个 API。当它被调用的时候,会以最佳的方式去从内存中清除积累的未引用对象(例如 garbage)。

谁去调用 System.gc()?

可以在应用栈中的各个部分来调用 System.gc()

  • 应用开发人员显式调用 System.gc()
  • 有时,System.gc() 会被三方库、框架或者是应用服务器所调用
  • 通过使用 JMX 由外部工具(如 VisualVM)来调用
  • 如果你的应用使用 RMI,RMI 会定期调用 System.gc()

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

当从你的应用中调用 System.gc() 或者 Runtime.getRuntime().gc() 时,stop-the-world,完整的 GC 事件都会被触发。在 stop-the-world 期间,整个 JVM 会被冻结(例如所有进行的客户交易都会被停止)。通常,这些完整的 GC 动作会花费相当长的一段时间。因此,当不需要进行 GC 但被执行了的时候,有可能会导致糟糕的用户体验和服务停止。

当触发 GC 的时候,JVM在整个过程中都会使用复杂的算法来进行一切的计算活动。当你调用 System.gc() 的时候,所有这些计算活动都会折腾掉。如果 JVM 在你开始调用 System.gc() 的一毫秒钟前触发了 GC 的话,应该怎么办?因为在你的应用中并不知道 GC 是什么时候被执行的。

那么有好的时机去执行 System.gc() 吗?

我们没有去述数应用中调用 System.gc() 的时机有哪些,但是这里我们在一个高级航空的应用中看到了一个有趣的例子。这个应用使用 1TB 的内存,整个 GC 过程花费将近5分钟的时间。是的,千万不要感到惊讶,是5分钟(但是我们也见过用到23分钟的情况)。为了避免这段暂停时间对顾客造成影响,这个航空公司想到了一个聪明的解决方法。在每晚,他们会从负载均衡服务器上取出一个 JVM 实例,然后他们通过 JVM 上的 JMX 显式触发 System.gc()。当 GC 事件完成并且 garbage 从内存中拉出后,他们会将这个实例加载回到复杂均衡池上。通过这个明智的方法,将这5分钟 GC 导致的暂定时间对顾客的影响减至最少。

如何检测 System.gc() 的调用是来自于你的应用呢?

你可能已经在 "谁去调用 System.gc()?" 章节中注意到,你可以看到 System.gc() 是可以从多个源头被调用的,而不仅仅只从你的应用中。因此,搜索你的源代码时,System.gc() 这段代码并不能代表你的应用在进行 System.gc()。这里就引起了一个疑惑:我们应该怎么检测到在整个应用过程中是否有调用到 System.gc()

这就到了 GC log 派上用处的时候了。赶紧在应用中启用 GC log吧。事实上,编者建议大家在所有生产服务器中启用 GC 日志,因为它可以帮助你排除故障以及优化应用性能,同时启用 GC log 所产生的消耗是可以忽略的。之后,将 GC log 上传到诸如 GCeasy、HP JMeter 等的 GC 日志分析工具当中,它们会产生详细的 GC 分析日志给到我们。

上图是 GCeasy 生成的报告中关于 'GC 发起'的一部分。你能看到 System.gc() 被调用了 304 次,占用了 GC 暂停时间的 52.42%。

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

你能通过下面的解决方法来移除显式的 System.gc() 调用:

a. 查询并替换

这也许是一个较为传统的方法 :-),但确实有效。在你的源代码查找 System.gc() 以及 Runtime.getRuntime().gc()。如果找到匹配,将其移除。这个方法在源代码中带有 System.gc() 是奏效。如果 System.gc() 是从你的三方库中、框架或者其他源头发起的话,这种方法就行不通了。在这种情况下,可以选择 b 方法。

b. -XX:+DisableExplicitGC

当你启动应用的时候,可以通过 JVM 参数 -XX:+DisableExplicitGC 来强制禁止 System.gc()。这个参数会禁用应用中所有的 System.gc() 调用。

c. RMI

如果你应用中使用 RMI,就可以自定义控制 System.gc() 的调用频率了。当启动应用时,可以使用以下的参数来配置频率:

  • -Dsun.rmi.dgc.server.gcInterval=n
  • -Dsun.rmi.dgc.client.gcInterval=n
  • 在版本中这些参数的默认值:
    • JDK 1.4.2 和 5.0 是 60000 毫秒(即60秒)
    • JDK 6 和更后的版本是 3600000 毫秒(即60分钟)

最后,你也可以将这些属性设置为非常高的值,用以将影响降至最低。

分享
点赞0
打赏
上一篇:Docker常用命令笔记(一)
下一篇:翻译 | 我们真的需要这么多依赖注入吗?