经过大量的搜索和研究,我了解到以下内容:
-XX:+UseParallelGC - 这使得 GC 在年轻代使用多个线程,但对于老年代仍使用串行标记和压缩算法。
-XX:+UseParallelOldGC - 这使得 GC 在老年代使用并行标记和压缩算法。
让我们来理解一下 -
在年轻代中工作的算法和内存布局(例如标记和复制、交换空间等),在老年代中由于很多原因而无法工作。
低死亡率 - 在老年代中,“死亡率”显著低于年轻代。在典型的 Java 应用程序中,大多数对象很快就消失了,只有少数生存更长时间。随着在年轻代幸存并提升到老年代的对象们,这些对象倾向于生存更长的时间。这导致老年代的死亡率比年轻代非常低。
大小显著 - 旧一代比年轻一代大得多。因为年轻一代很快被清理,相对较少的空间可用于许多短寿命对象(小年轻一代)。在老一代中,随着时间的推移,对象会积累。因此,老一代中必须有比年轻一代更多的空间(大老一代)。
分配较少 - 在老一代中,分配比年轻一代少得多。这是因为在老一代中,只有当垃圾收集器将幸存的对象从年轻一代晋升到老一代时才会产生对象。另一方面,在年轻一代中,应用程序生成的所有对象(即大多数分配)都发生在年轻一代中。
考虑到这些差异,已选择了一个 Young Generation 的算法,以尽快完成垃圾收集,因为由于高死亡率 [点(1)],它必须经常被调用。此外,该算法必须确保尽可能高效的内存分配 [点(3)],因为大部分分配发生在年轻一代中。Young Generation 上的标记-复制算法具有这些属性。
另一方面,这个算法在旧代上没有意义。情况是不同的:垃圾收集器必须处理旧代中许多对象 [点(2)],其中大部分仍然存活;只有一小部分变得不可达并可以释放 [点(1)]。如果垃圾收集器每次进行垃圾收集时都像标记-复制一样复制所有幸存对象,那么它将花费很长时间进行复制而几乎不会获得任何好处。
因此,标记-清除算法用于旧代,其中不复制任何内容,仅释放不可达对象。由于此算法导致堆的碎片化,因此人们考虑了标记-清除算法的变体,在扫描阶段后进行压缩,从而减少了碎片化。这种算法被称为标记-整理算法。
标记-整理算法可能需要消耗大量时间,因为它需要按照以下四个阶段遍历对象图:
- 标记
- 计算新位置
- 引用调整
- 移动
在
计算新位置阶段中,每当它获得一个空闲空间时,尝试找到一个可以移动到该空间的对象(
碎片整理)。将该对存储以供后续阶段使用。这会导致算法花费更多时间。
虽然标记和比较解决了一些特定于老年代的问题,但它也存在严重问题,因为这是一个STW(停止整个世界)事件,并且需要很长时间,可能会严重影响应用程序。
老年代的替代算法
为了减少中断时间,已经考虑了一些串行标记-压缩算法的替代方法:
并行标记-压缩算法仍然锁定所有应用程序线程,但然后使用多个垃圾收集器线程处理标记和随后的压缩。虽然这仍然是一种整个世界停止的方法,但在多核或多处理器机器上产生的暂停时间比串行标记-压缩算法短。这个老年代的并行算法(称为“ParallelOld”)自Java 5 Update 6以来就已经可用,并使用选项
-XX: + UseParallelOldGC进行选择。
一种竞争的标记-清除算法,至少部分地与应用程序竞争,偶尔需要短暂的“停止-全球”阶段。这个并发的标记和清除算法(称为“CMS”)自Java 1.4.1以来就存在了;它通过选项-XX:+UseConcMarkSweepGC打开。重要的是,这只是一个标记和清除算法;不进行压缩,导致已经讨论过的碎片问题。
因此,在简洁起见,-XX: + UseParallelOldGC被用作使用标记和压缩算法进行主要收集时使用多个线程的指示。如果使用这个选项,小的或年轻的收集是并行的,但是大的收集仍然是单线程的。
希望这回答了您的问题。