大多数JVM将堆分为三代 - 年轻一代(YG),老一代(OG)和永久一代(也称为终身代)。这些堆后面是什么?
实证研究表明,大多数所创建的对象的寿命都很短 -
资源
正如您所看到的那样,随着时间的推移越来越多的对象被分配,幸存的字节数变得越来越少(通常)。Java对象的死亡率很高。
下面来看一个简单的例子。Java中的String
类是不可变的。每次需要更改String
对象的内容时,都必须完全创建一个新对象。假设在循环中对字符串进行了1000
次更改,如下面的代码所示 -
String str = “G11 GC”; for(int i = 0 ; i < 1000; i++) { str = str + String.valueOf(i); }
在每个循环中,需要创建一个新的字符串对象,并且在上一次迭代期间创建的字符串变得无用(即,它不被任何引用)。该对象的T
生命周期只是一次迭代 - 它们将立即被GC收集。这种短寿命的对象保存在堆的年轻一代(YG)区域。从年轻一代(YG)收集对象的过程称为次要垃圾收集,它总是导致“停止世界”暂停。
当年轻一代(YG)被填满时,GC会进行小规模的垃圾收集。丢弃死对象,并将活动对象移动到老一代(OG)。应用程序线程在此过程中停止。
在这里,我们可以看到这种代设计提供的优势。年轻一代只是堆的一小部分,很快就会被填满。但处理它比处理整个堆所花费的时间要少很多。所以,在这种情况下,“停止世界”的停顿要短得多,尽管更频繁。我们应该始终瞄准较长时间的暂停,即使它们可能更频繁。我们将在本教程的后续部分详细讨论这个问题。
年轻一代分为两个空间 - survivor 和 eden 空间。在收集 eden期间幸存下来的对象被移动到survivor空间,survivor空间中存活的对象移动到老一代。年轻一代在被收集时被压缩。
随着对象移动到旧一代,它最终会被填满,并且必须被收集和压缩。不同的算法采用不同的方法。它们中的一些停止了应用程序线程(由于老一代与年轻代相比非常大,因此导致长时间“停止世界”暂停),而其中一些线程在应用程序线程继续运行时同时执行。此过程称为完整GC。两个这样的收集器是CMS和G1。
现在让我们详细分析这些算法。
串行GC
它是客户端类计算机上的默认GC(单处理器计算机或32b JVM,Windows)。通常,GC是多线程的,但串行GC不是。它有一个单独的线程来处理堆,并且它会在执行次要GC或主要GC时停止应用程序线程。可以通过指定标志来命令JVM使用此GC: -XX:+ UseSerialGC
。如果我们希望它使用一些不同的算法,请指定算法名称。请注意,旧一代在主要GC期间完全压缩。
吞吐量GC
此GC在64b JVM和多CPU计算机上是默认的。与串行GC不同,它使用多个线程来处理年轻一代。因此,GC也称为并行收集器。我们可以通过使用标志来命令我们的JVM使用此收集器:-XX:+ UseParallelOldGC
或-XX:+ UseParallelGC
(从JDK 8开始)。应用程序线程在执行主要或次要垃圾回收时停止。与串行收集器一样,它在主要GC期间完全压缩年轻一代。
吞吐量GC收集YG和OG。当伊甸园已经填满时,收集器会将活动对象从其中弹出到OG或其中一个幸存者空间(下图中的SS0和SS1)。丢弃死对象以释放它们占用的空间。
在YG的GC之前
在YG的GC之后
在完整GC期间,吞吐量收集器会清空整个YG,SS0和SS1。操作后,OG仅包含活动对象。我们应该注意,上述两个收集器在处理堆时都会停止应用程序线程。在主要GC期间长时间“停止世界”暂停。接下来的两个算法旨在消除它们,代价是更多的硬件资源 -
CMS收集器
它代表’并发标记扫描’。它的功能是它使用一些后台线程定期扫描旧代,并清除死对象。但在次要GC期间,应用程序线程将停止。但是,暂停非常小。这使CMS成为低暂停收集器。
在运行应用程序线程时,此收集器需要额外的CPU时间来扫描堆。此外,后台线程只收集堆并且不执行任何压缩。它们可能导致堆变得支离破碎。随着这种情况的继续,在一段时间之后,CMS将停止所有应用程序线程并使用单个线程压缩堆。使用以下JVM参数告诉JVM使用CMS收集器 -
XX:+UseConcMarkSweepGC -XX:+UseParNewGC
作为JVM参数告诉它使用CMS收集器。
在GC之前
在GC之后
请注意,该集合同时进行。
G1 GC
该算法通过将堆分成多个区域来工作。与CMS收集器一样,它在执行次要GC时停止应用程序线程,并使用后台线程来处理旧代,同时保持应用程序线程的运行。由于它将旧一代划分为区域,因此在将对象从一个区域移动到另一个区域的同时不断压缩它们。因此,碎片是最小的。可以使用标志:XX:+ UseG1GC
指示JVM使用此算法。与CMS一样,它还需要更多的CPU时间来处理堆并同时运行应用程序线程。
该算法设计用于处理较大的堆(>4G),它分成许多不同的区域。其中一些地区包括年轻一代,其余地区包括旧年代。使用传统方式清除YG - 停止所有应用程序线程以及所有仍然存在于旧代或幸存者空间的对象。
请注意,所有GC算法都将堆分为YG和OG,并使用STWP清除YG。这个过程通常非常快。