JVM 分代GC

  • 分代GC

    大多数JVM将堆分为三代 - 青年一代(YG),老一代(OG)和永久一代(也称为终身一代)。这种想法背后的原因是什么?
    实证研究表明,大多数已创建的对象的寿命都很短-
    jvm gc
    如您所见,随着时间分配的对象越来越多,幸存的字节数变少了(通常)。Java对象的死亡率很高。我们将看一个简单的例子。Java中的String类是不可变的。这意味着每次您需要更改String对象的内容时,都必须完全创建一个新对象。让我们假设您在一个循环中对该字符串进行了1000次更改,如以下代码所示-
    
    String str = “G11 GC”;
    
    for(int i = 0 ; i < 1000; i++) {
       str = str + String.valueOf(i);
    }
    
    在每个循环中,我们创建一个新的字符串对象,并且在上一次迭代期间创建的字符串变得无用(也就是说,没有任何引用对其进行引用)。该对象的生存期只是一个迭代-它们将立即由GC收集。这种短暂的对象保留在堆的年轻代区域中。从年轻一代收集对象的过程称为次要垃圾收集,它始终导致“时间凝滞”的暂停。随着年轻一代的忙碌,GC会进行少量垃圾回收。废弃的对象将被丢弃,而有生命的对象将被转移到上一代。应用程序线程在此过程中停止。
    在这里,我们可以看到这种分代设计所提供的优势。年轻一代只是一小部分,很快就被填补了。但是处理所需的时间比处理整个堆所需的时间少得多。因此,在这种情况下,“时间凝滞”的停顿时间要短得多,尽管更频繁。我们应该始终将较短的停顿放在更长的停顿上,即使它们可能更频繁。我们将在本教程的后续部分中对此进行详细讨论。
    年轻一代分为两个空间 - 伊甸园幸存者空间。在收集伊甸园后幸存的对象被移至幸存者空间,而在幸存者空间中幸存的对象则被移交给了老一代。年轻的一代在收集时被压缩。随着对象移交给旧的一代,它最终会填满,必须进行收集和压缩。不同的算法对此采取不同的方法。他们中的一些对象停止了应用程序线程(由于老一辈比年轻一代要大得多,这导致了较长的“时间凝滞”暂停),而其中一些则在应用程序线程保持运行的同时进行。此过程称为完整GC。CMS和G1是两个这样的收集器。
    现在让我们详细分析这些算法。
  • 串行GC

    它是客户端级计算机(单处理器计算机或32b JVM,Windows)上的默认GC。通常,GC具有大量的多线程功能,而串行GC则不是。它只有一个线程来处理堆,并且在执行次要GC或主要GC时将停止应用程序线程。我们可以通过指定标志-XX:+UseSerialGC来命令JVM使用该GC 。如果我们希望它使用其他算法,请指定算法名称。请注意,在大型GC中,旧一代已完全压缩。
  • 吞吐量GC

    该GC在64b JVM和多CPU机器上是默认的。与串行GC不同,它使用多个线程来处理年轻一代和老一代。因此,GC也称为并行收集器。我们可以使用以下标志来命令JVM使用该收集器:-XX:+ UseParallelOldGC或-XX:+UseParallelGC(JDK 8及更高版本可用)。应用程序线程在进行主要或次要垃圾回收时会停止。像串行收集器一样,它可以在大型GC中完全压缩年轻一代。吞吐量GC收集YG和OG。伊甸园装满后,收集器会将对象从其中弹出,进入OG或幸存者空间之一(下图中的SS0和SS1)。废弃的对象将被丢弃以释放它们所占据的空间。
    YG在GC之前
    jvm gc
    YG在GC之后
    jvm gc
    在完整GC期间,吞吐量收集器将清空整个YG,SS0和SS1。操作后,OG仅包含活动对象。我们应该注意,上述两个收集器在处理堆时都会停止应用程序线程。这意味着在大型GC中,“时间凝滞”会长时间停顿。接下来的两种算法旨在消除它们,但要消耗更多的硬件资源-
  • CMS收集器

    它代表“并发标记扫描”。它的功能是它使用一些后台线程来定期扫描旧版本,并清除死对象。但是在次要GC期间,应用程序线程将停止。但是,停顿很小。这使得CMS成为低中断的收集器。在运行应用程序线程时,此收集器需要额外的CPU时间来浏览堆。此外,后台线程仅收集堆,并且不执行任何压缩。它们可能导致堆变得碎片化。如此一来,在一定时间后,CMS将停止所有应用程序线程,并使用单个线程压缩堆。使用以下JVM参数告诉JVM使用CMS收集器-
    XX:+ UseConcMarkSweepGC -XX:+ UseParNewGC作为JVM参数告诉它使用CMS收集器。
    GC之前
    jvm gc
    GC之后
    jvm gc
    请注意,收集是同时进行的。
  • G1 GC

    该算法通过将堆划分为多个区域来工作。与CMS收集器一样,它在执行次要GC时会停止应用程序线程,并在保持应用程序线程运行的同时使用后台线程来处理旧版本。由于将旧一代划分为多个区域,因此在将对象从一个区域移动到另一个区域时,它会继续压缩它们。因此,碎片最少。您可以使用标志:XX:+ UseG1GC来告诉您的JVM使用此算法。与CMS一样,它也需要更多的CPU时间来处理堆并同时运行应用程序线程。该算法已设计为处理更大的堆(> 4G),这些堆被划分为多个不同的区域。这些区中有一些是年轻一代,其余则是老一代。传统上使用YG清除-停止所有应用程序线程,并且所有仍旧存在于旧分代或幸存者空间中的对象。
    请注意,所有GC算法将堆分为YG和OG,并使用STWP清除YG。这个过程通常非常快。