【问题标题】:Why does java wait so long to run the garbage collector?java 为什么要等这么久才能运行垃圾收集器?
【发布时间】:2011-10-30 04:32:59
【问题描述】:

我正在使用Play! Framework 构建一个Java Web 应用程序。我在playapps.net 上托管它。我一直对提供的内存消耗图表感到困惑。这是一个示例:

该图表来自一段持续但名义上的活动。我没有采取任何措施来触发内存衰减,所以我认为这是因为垃圾收集器运行时几乎达到了允许的内存消耗。

我的问题:

  • 假设我的应用程序没有有内存泄漏对我来说公平吗,因为看起来垃圾收集器在运行时正确回收了所有内存?
  • (来自标题)为什么java要等到最后一秒才能运行垃圾收集器?随着内存消耗增长到图表的前四分之一,我发现性能显着下降。
  • 如果我上面的断言是正确的,那么我该如何解决这个问题?我在 SO 上阅读的其他帖子似乎反对对 System.gc() 的调用,范围从中立(“它只是运行 GC 的请求,因此 JVM 可能会忽略你”)到完全反对(“依赖于 @987654327 的代码@ 从根本上被破坏了”)。还是我在这里偏离了基地,我应该在自己的代码中寻找导致这种行为和间歇性性能损失的缺陷?

更新
我已经在 PlayApps.net 上针对这个问题展开了讨论,并在这里提到了一些要点;特别是@Affe 关于完整 GC 设置的评论非常保守,@G_H 关于初始和最大堆大小设置的评论。

这是link to the discussion,不过很遗憾,您需要一个 playapps 帐户才能查看它。

我收到反馈后会在这里报告;非常感谢大家的回答,我已经从他们那里学到了很多东西!

分辨率
Playapps 支持仍然很棒,但对我没有太多建议,他们唯一的想法是,如果我广泛使用缓存,这可能会使对象的存活时间超过需要的时间,但事实并非如此。我仍然学到了很多东西(哇哦!),我给了@Ryan Amos 绿色的支票,因为我接受了他的建议,即每半天打电话给System.gc(),目前效果很好。

【问题讨论】:

  • 我不太了解 play 框架,无法就其内存占用给您一个明确的答案,但是 SAP 提供了一个很好的工具来分析内存分配,现在在 eclipse.org/mat 我认为这可能会有所帮助您可以对内存泄漏做出断言而不是假设。
  • 可能导致性能问题的一件事是交换;如果您没有足够的物理内存用于该堆(例如,它是一个共享机器,有许多其他进程使用大量内存),将页面交换到磁盘会降低 VM 的性能。
  • @rds Play 以非常小的内存占用而自豪,而 PlayApps.net(我正在使用的托管服务)本身占用的内存比我展示的要小。谢谢MAT推荐,我去看看!
  • 另外,我同意 +vanza 关于交换灾难的看法。根据我在 SharePoint 上的(糟糕的)经验,我记得为进程提供过多内存并非总是最佳性能策略。你能改变 JVM 设置并设置 -Xmx 80m,看看它的行为如何?
  • @Raedwald:我以前没听说过。 32 位 JVM 的最大堆大小有一个限制,远低于如今的平均 RAM 内存容量。 64 位 JVM 不再是这种情况。但即便如此,如果主机上正在运行许多进程并且其内存已满,则交换将开始发生。这是由操作系统处理的,JVM 对此一无所知。它只会分配内存,不知道其中一些可以映射到磁盘。

标签: java memory memory-management playframework


【解决方案1】:

任何详细的答案都将取决于您使用的垃圾收集器,但有些东西在所有(现代、sun/oracle)GC 中基本相同。

每次你看到图表中的使用量下降,那就是垃圾回收。释放堆的唯一方法是通过垃圾收集。问题是有两种类型的垃圾收集,次要的和完整的。堆被分成两个基本的“区域”。年轻有为。 (实际上还有更多的子组。)任何在 Young 中占用空间并且在 Minor GC 出现以释放一些内存时仍在使用的东西,都将被“提升”为tenured。一旦某个东西跃升为终身,它就会无限期地停留,直到堆没有可用空间并且需要进行完整的垃圾回收。

因此,对该图的一种解释是,您的年轻代相当小(默认情况下,在某些 JVM 上,它可能占总堆的相当小百分比),并且您使对象“活动”了相当长的时间。 (也许您在 Web 会话中持有对它们的引用?)因此,您的对象在被提升到永久空间之前一直在“幸存”垃圾收集,在那里它们无限期地停留,直到 JVM 运行良好且真正内存不足。

同样,这只是与您拥有的数据相符的一种常见情况。需要有关 JVM 配置和 GC 日志的完整详细信息才能真正确定发生了什么。

【讨论】:

  • 这听起来很像我在本地分析我的应用程序时看到的(使用 YourKit)。如您所见,playapps.net 提供的图表并不那么详细,但 YourKit 向我展示了“年轻”一代的内存锯齿模式,而“老”一代稳步增长,直到垃圾回收。但是,这些都不会导致性能下降,对吗?我是否应该寻找其他地方来解释为什么应用程序在内存位于图表的顶部 1/4 时会变慢,然后在内存被回收后又会加快速度?
  • 奇怪的是,JVM 会在运行完整收集之前将自己“压力”到那种程度,以找出每一点空间。您控制 JVM 还是您的托管公司提供的环境?他们可能出于自己的原因将其配置为这样做。
  • 全部由托管公司 PlayApps.net 在 VPS 服务器上控制。你认为值得联系他们看看他们是否有建议吗?
  • 是的,您可能会提到,当完整收集触发器如此吝啬以至于次要收集的频率在完整收集发生之前很久就会影响性能时,它看起来像是设置。也有可能它正在使用只有一个处理器内核的并发垃圾收集器,这可能会以与您的问题一致的方式运行。
  • @goggin13 “奇怪的是,JVM 在运行完整收集之前会‘强调’自己到那种程度,以找出每一点空间”我不太确定它是否可以轻松完成另一种方式。一旦 GC 确定某物是垃圾,不移除该垃圾将是一种浪费。确定哪些对象是垃圾可能代价高昂。
【解决方案2】:

Java 不会运行垃圾清理器,除非它必须运行,因为垃圾清理器会减慢速度,不应该频繁运行。我认为您可以更频繁地安排一次清洁,例如每 3 小时一次。如果应用程序从不消耗全部内存,那么就没有理由运行垃圾清理器,这就是 Java 仅在内存非常高时才运行它的原因。

所以基本上,不要担心别人怎么说:做最有效的事。如果您发现以 66% 的内存运行垃圾清理程序可以提高性能,请执行此操作。

【讨论】:

  • 谢谢!我会试一试,看看是否有帮助。
  • 赞成。不要听那些说“永远不要这样做”的人。如果它有效,不会使事情变得非常复杂,不会创建不可维护的架构,并且没有更好的解决方案,那就去做吧。
  • @G_H 人们没有其他理由不要求永远不要这样做:P
【解决方案3】:

我注意到图表在下降之前并未严格向上倾斜,但局部变化较小。虽然我不确定,但我认为如果没有进行垃圾回收,内存使用不会出现这些小幅下降。

Java 中有次要集合和主要集合。次要收集经常发生,而主要收集很少见并且会更多地降低性能。次要集合可能倾向于清除诸如在方法中创建的短期对象实例之类的东西。一个主要的集合将删除更多,这可能是在图表末尾发生的情况。

现在,我在打字时发布的一些答案很好地解释了垃圾收集器、对象生成等方面的差异。但这仍然不能解释为什么要花这么长的时间(将近 24 小时)才能完成认真的清洁。

在启动时可以为 JVM 设置的两件事是允许的最大堆大小和初始堆大小。最大值是一个硬限制,一旦达到该值,进一步的垃圾回收不会减少内存使用量,如果您需要为对象或其他数据分配新空间,您将收到 OutOfMemoryError。但是,内部也有一个软限制:当前堆大小。 JVM 不会立即吞噬最大内存量。相反,它从您的初始堆大小开始,然后在需要时增加堆。可以把它想象成 JVM 的 RAM,它可以动态增加。

如果您的应用程序的实际内存使用量开始达到当前堆大小,通常会发起垃圾回收。这可能会减少内存使用,因此不需要增加堆大小。但也有可能应用程序当前确实需要所有内存并且会超过堆大小。在这种情况下,如果它尚未达到最大设置限制,则会增加它。

现在,您的情况可能是初始堆大小设置为与最大值相同的值。假设是这样,那么 JVM 将立即占用所有内存。应用程序需要很长时间才能积累足够的垃圾以达到内存使用的堆大小。但在那一刻,你会看到一个大集合。从一个足够小的堆开始并允许它增长,从而将内存使用限制在所需的范围内。

这是假设您的图表显示堆使用而不是分配的堆大小。如果不是这种情况,并且您实际上看到堆本身是这样增长的,那么还有其他事情正在发生。我承认我对垃圾收集的内部结构及其调度不够了解,无法完全确定这里发生了什么,其中大部分来自对分析器中泄漏应用程序的观察。所以如果我提供了错误的信息,我会记下这个答案。

【讨论】:

  • 这真的很有趣;我不知道您关于将 JVM 初始堆大小设置为与最大堆大小相同的观点。我无权访问此托管帐户的 JVM 设置,但我想我会开一张支持他们两美分的票,我会看看他们是否认为这可能是个问题。谢谢!
  • 它可能会发生。人们有时会想“嘿,我只需将 start 和 max 设置为相同,这样 Java 就不必为这个堆大小调整业务而烦恼了”。但实际上,这实际上可能会降低性能。如果您不关心有大量未使用的内存在一段时间内闲置,那么它可能是有意义的。但大多数情况下,您会希望与系统中需要内存的其他进程配合得很好。
  • 您认为这会与性能提升齐头并进吗?其他帖子建议这种内存使用不应该真正影响我的应用程序。另外,您认为对 System.gc() 的显式调用可能有助于启动早期的集合吗?
  • 好吧,由于您运行的环境可能包含许多其他 JVM 进程,因此有很多因素会影响性能。例如交换,正如您帖子中的 cmets 中 vanza 所提到的那样。为 JVM 提供更高的最大堆实际上可能对您不利,因为它可能会使用比必要更多的内存,从而阻碍其他进程。关于 System.gc(),它经常被看不起,但我不喜欢绝对的东西。即使是最糟糕的反模式有时也是奇怪情况下的正确答案。你可以试一试,看看会发生什么。让它在可预测的时间运行。
  • 这两件事我都会做,稍后再回来报告!非常感谢您的帮助,我真的很感激
【解决方案4】:

您可能已经注意到,这不会影响您。垃圾收集仅在 JVM 认为需要运行时才会启动,并且这是为了优化而发生的,如果您可以进行一次完整收集并进行完整清理,那么进行许多小型收集是没有用的。

当前的 JVM 包含一些非常有趣的算法,并且垃圾收集本身 id 分为 3 个不同的区域,您可以找到更多关于此here 的信息,这是一个示例:

三种收集算法

HotSpot JVM 提供了三种 GC 算法,每种算法都针对特定代中特定类型的收集进行了调整。复制(也称为清除)集合快速清理新生代堆中的短期对象。 mark-compact 算法采用更慢、更健壮的技术来收集老年代堆中寿命更长的对象。增量算法试图通过执行稳健的 GC 来改进老年代收集,同时最大限度地减少暂停。

复制/清除收集

使用复制算法,JVM 仅通过进行小的清理——一个用于收集和移除垃圾的 Java 术语——来回收新一代对象空间(也称为伊甸园)中的大多数对象。寿命更长的对象最终会被复制或永久保存到旧的对象空间中。

标记紧凑集合

随着越来越多的对象成为永久对象,旧对象空间开始达到最大占用率。用于在旧对象空间中收集对象的标记压缩算法与在新对象空间中使用的副本收集算法具有不同的要求。

mark-compact 算法首先扫描所有对象,标记所有可达对象。然后它压缩所有剩余的死对象间隙。 mark-compact算法比copy collection算法占用更多时间;但是,它需要更少的内存并消除内存碎片。

增量(训练)收集

新一代的复制/清除和老一代的标记压缩算法不能消除所有的 JVM 暂停。这种停顿与活动对象的数量成正比。为了满足无暂停 GC 的需求,HotSpot JVM 还提供增量或训练收集。

增量收集将旧的对象收集暂停分解为许多微小的暂停,即使对象区域很大。这个算法不仅仅是一个新的和一个老一代,还有一个包含许多小空间的中间代。增量收集有一些开销;您可能会看到多达 10% 的速度下降。

-Xincgc 和 -Xnoincgc 参数控制您如何使用增量收集。 HotSpot JVM 的下一个版本 1.4 版将尝试连续、无暂停的 GC,这可能是增量算法的一种变体。我不会讨论增量收集,因为它很快就会改变。

这种分代垃圾收集器是我们目前解决该问题的最有效解决方案之一。

【讨论】:

  • 感谢您的链接!和总结。如果您看到我的评论@Affe 的回复,尽管这似乎确实影响了我。我看到性能下降,因为内存消耗进入图表的顶部 1/4,然后它似乎在 GC 运行后反弹。这更有可能是巧合,我应该在其他地方寻找性能问题吗?
  • 这是非常古老的信息,从 2002 年开始,关于 Java 1.3.1。最新的 JVM 使用不同的算法。这里有一些关于 Java 6 oracle.com/technetwork/java/javase/gc-tuning-6-140523.html 的信息,Java 7 也有新的 G1 垃圾收集器。
  • @esko 几代人(这就是本文所涵盖的内容)完全相同并且没有改变。有关最新算法的更多信息,还有这个 - oracle.com/technetwork/java/…
【解决方案5】:

我有一个应用程序可以生成这样的图表并按照您的描述进行操作。我正在使用 CMS 收集器 (-XX:+UseConcMarkSweepGC)。这是我的情况。

我没有为应用程序配置足够的内存,所以随着时间的推移,我遇到了堆中的碎片问题。这导致 GC 的频率越来越高,但它实际上并没有抛出 OOME 或从 CMS 失败到串行收集器(在这种情况下应该这样做),因为它保留的统计信息只计算应用程序暂停时间(GC 块世界),这些计算忽略应用程序并发时间(GC 与应用程序线程一起运行)。我调整了一些参数,主要是给它一个完整的垃圾加载更多的堆(有一个非常大的新空间),设置 -XX:CMSFullGCsBeforeCompaction=1,问题就停止了。

【讨论】:

    【解决方案6】:

    您可能确实存在每 24 小时清除一次的内存泄漏。

    【讨论】:

    • “清除”是什么意思?内存泄漏不是无法清除吗?我没有重新启动应用程序,内存下降(我假设)来自 GC 运行。如果存在内存泄漏,GC 是否无法回收该内存?如果我错了,不要害羞,我只是想掌握一切!感谢您的想法!
    • 假设你的应用有一个不断增长的列表;并且每隔 24 小时,列表就会被您的代码清空。
    猜你喜欢
    • 1970-01-01
    • 2010-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多