是什么导致JVM进行重大垃圾回收?

问题描述 投票:8回答:4

我有一个Java应用程序,它在不同的环境中显示不同的GC行为。在一个环境中,堆使用图是一个缓慢的锯齿,每10个小时左右就有一个主要的GC,只有当堆大于90%时才会填满。在另一个环境中,JVM每小时在点上执行主要GC(在这些时间堆通常在10%到30%之间)。

我的问题是,导致JVM决定执行主要GC的因素是什么?

显然它会在堆几乎满了时收集,但是还有一些其他原因在起作用,我猜这与我应用程序中的每小时计划任务有关(尽管此时内存使用没有峰值)。

我假设GC行为在很大程度上取决于JVM;我在用:

  • Java HotSpot(TM)64位服务器VM 1.7.0_21 Oracle Corporation
  • 没有特定的GC选项,因此使用64位服务器的默认设置(PS MarkSweep和PS Scavenge)

其他信息:

  • 这是在Tomcat 6中运行的Web应用程序。
  • Perm gen在两种环境中都徘徊在10%左右。
  • 具有锯齿行为的环境具有7Gb最大堆,另一个具有14Gb。

拜托,没有猜测。 JVM必须具有用于决定何时执行主要GC的规则,并且这些规则必须在源中深处编码。如果有人知道它们是什么,或者它们被记录在哪里,请分享!

java garbage-collection jvm jvm-hotspot
4个回答
6
投票

垃圾收集是一个pretty complicated topic,虽然你可以了解这方面的所有细节,但我认为在你的情况下发生的事情非常简单。

Sun的Garbage Collection Tuning guide,在“明确的垃圾收集”标题下,警告:

应用程序可以与垃圾收集进行交互...通过显式调用完整的垃圾收集...这可以强制在可能不需要时完成主要收集...显式垃圾收集的最常见用途之一是RMI ... RMI强制定期收集完整收集

该指南说垃圾收集之间的默认时间是一分钟,但sun.rmi Properties reference下的sun.rmi.dgc.server.gcInterval说:

默认值为3600000毫秒(一小时)。

如果您在一个应用程序中每小时都看到主要集合而不是另一个应用程序,那可能是因为应用程序正在使用RMI,可能只在内部,并且您没有将-XX:+DisableExplicitGC添加到启动标志。

禁用显式GC,或通过设置-Dsun.rmi.dgc.server.gcInterval=7200000并观察GC是否每两小时发生一次来测试此假设。


6
投票

我发现了四个可能导致主要GC的条件(给定我的JVM配置):

  1. 旧的gen区域已满(即使它可以生长,主要GC仍将首先运行)
  2. perm gen区域已满(即使它可以生长,主要GC仍将首先运行)
  3. 有人手动调用System.gc():一个坏的库或与RMI相关的东西(参见链接123
  4. 年轻一代的地区都已满,没有任何东西可以搬进老一代(见1

正如其他人所评论的那样,可以通过分配大量的堆和permgen,并将-Xms-Xmx设置为相同的值(以及perm等效值)来改进案例1和案例2,以避免动态堆调整大小。

使用-XX:+DisableExplicitGC标志可以避免使用案例3。

案例4需要更多参与调整,例如-XX:NewRatio=N(参见Oracle's tuning guide)。


3
投票

这取决于您的配置,因为HotSpot在不同的Java环境中以不同的方式配置自己。例如,在具有2GB以上和两个处理器的服务器中,某些JVM将以“-server”模式配置,而不是默认的“-client”模式,它们以不同的方式配置内存空间(代)的大小,并具有何时会发生垃圾收集的影响。

完整的GC可以自动发生,但也可以在代码中调用垃圾收集器(例如:使用System.gc())。它自动取决于次要集合的行为方式。

至少使用了两种算法。如果使用默认值,则复制算法用于次要集合,而标记扫描算法用于主要集合。

复制算法包括将已使用的内存从一个块复制到另一个块,然后清除包含块的空间而不引用它们。 JVM中的复制算法使用大面积用于第一次创建的对象(称为Eden)和两个较小的对象(称为survivors)。幸存的物体从Eden复制一次,并在每个次要集合中从survivor空间复制几次,直到它们成为终身并被复制到另一个空间(称为tenured空间),在那里它们只能在主要集合中被移除。

Eden中的大多数对象快速死亡,因此第一个集合将幸存的对象复制到幸存者空间(默认情况下更小)。有两个幸存者s1s2。每次Eden填充时,来自Edens1的幸存物体被复制到s2Edens1被清除。下一次,来自Edens2的幸存者被复制回s1。他们继续被从s1复制到s2s1,直到达到一定数量的副本,或者因为一个块太大而不适合,或者其他一些标准。然后将幸存的内存块复制到tenured代。

tenured对象不受次要集合的影响。它们会累积,直到区域变满(或者调用垃圾收集器)。然后JVM将在主要集合中运行标记扫描算法,该算法将仅保留仍具有引用的幸存对象。

如果您有更大的对象不适合幸存者,他们可能会被直接复制到tenured空间,这将更快地填充,您将更频繁地获得主要集合。

此外,幸存者空间的大小,s1s2之间的副本数量,与Edens1的大小相关的s2大小,终身代的大小,所有这些都可以在具有JVM人体工程学的不同环境中自动配置,这可能是自动选择-server-client行为。您可能会尝试将两个JVM作为-server-client运行,并检查它们是否仍然表现不同。


1
投票

即使这会降低投票...我最好的猜测(你必须测试这个)将是堆需要扩展,当这发生时,将触发一个完整的gc。并非所有内存都立即分配给JVM。

您可以通过将-Xms和-Xmx设置为相同的值来测试,例如每个7GB

© www.soinside.com 2019 - 2024. All rights reserved.