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

时间:2014-03-07 12:16:40

标签: java garbage-collection jvm jvm-hotspot

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

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

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

我认为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的规则,并且这些规则必须在源中深处编码。如果有人知道它们是什么,或者记录在哪里,请分享!

4 个答案:

答案 0 :(得分:6)

垃圾收集是pretty complicated topic,虽然您可以了解有关此问题的所有详细信息,但我认为您的案例中发生的事情非常简单。

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

  

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

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

  

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

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

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

答案 1 :(得分:5)

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

  1. 旧的Gen区域已满(即使可以种植,主要的GC仍会先运行)
  2. perm gen区域已满(即使它可以生长,主要GC仍会先运行)
  3. 有人手动调用System.gc():错误的库或与RMI相关的内容(请参阅链接123
  4. 年轻的一代人都已经满了,没有任何东西可以搬进老一代(见1
  5. 正如其他人所评论的那样,可以通过分配大量的堆和permgen,并将-Xms-Xmx设置为相同的值(以及perm等效值)来改进案例1和2,以避免动态堆调整大小。

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

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

答案 2 :(得分:2)

这取决于您的配置,因为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之间的副本数量,Eden大小与s1s2的大小相关,使用 JVM人机工程学,可以自动选择-server-client行为。您可以尝试将两个JVM作为-server-client运行,并检查它们是否仍然表现不同。

答案 3 :(得分:1)

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

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