系列中的上一篇
当前教程
Java 中的垃圾回收
系列中的下一篇

系列中的上一篇: 垃圾回收简介

系列中的下一篇: ZGC 概述

Java 中的垃圾回收

 

Java 中的垃圾回收

在上一节中,我们了解到 Java 使用垃圾回收器来进行内存管理。但是,垃圾回收器究竟是如何工作的呢?在本节中,我们将对此进行更深入的探讨。

垃圾回收器的类型

在 HotSpot JVM 中,垃圾回收器并非一个统一的概念,而是有多种实现方式。使用哪种垃圾回收器实现将取决于可用的硬件资源和应用程序的性能要求。

  • 串行垃圾回收器 - 在单个线程上执行所有垃圾回收操作。它具有较长的暂停时间,但资源使用率较低。最适合仅具有单个处理器的系统。
  • 并行垃圾回收器 - 与串行垃圾回收器类似,但可以利用多个线程来执行垃圾回收工作。
  • 并发标记清除 (CMS) 垃圾回收器 (在 JDK 9 中已弃用,在 JDK 14 中已移除) - 通过在应用程序运行时执行一些垃圾回收工作来减少垃圾回收暂停时间。
  • 垃圾优先 (G1) 垃圾回收器 (自 JDK 9 以来为默认值) - 改进了 CMS GC 并取代了它。G1 非常适合具有大量内存的多处理器机器。
  • ZGC (在 JDK 11 中为实验性,在 JDK 15 中为生产环境) - 超低延迟 GC,可以扩展到具有多 TB 堆的应用程序。ZGC 的内部实现和行为与其他列出的垃圾回收器截然不同,其行为的描述将在单独的文章中进行处理。

 

堆内存

堆内存是 JVM 控制的系统内存分配。JVM 可用的堆内存大小主要由 -Xms<value>-Xmx<value> JVM 参数控制。-Xms<value> 设置初始堆大小和最小堆大小。而 -Xmx<value> 将设置最大堆大小。

如果堆内存已满,当 JVM 尝试为新对象分配空间时,将导致 JVM 抛出 java.lang.OutOfMemoryError 异常。对于 Java 中的大多数垃圾回收器实现,堆内存根据对象的“年龄”被划分为多个区域。区域的数量和类型将根据垃圾回收器的具体实现而有所不同。

分代垃圾回收

Java 中的大多数垃圾回收器都是分代垃圾回收器。分代垃圾回收器旨在利用弱分代假设,该假设认为大多数对象都是短暂的。因此,分代垃圾回收器将堆分为年轻代和老年代。在分配时,对象从年轻代开始,年轻代中的对象会经常检查是否不再可达。如果一个对象在足够多的垃圾回收周期中存活下来,它将被复制到老年代,老年代的检查频率较低。

分代垃圾回收器提供的优势是更有效地利用 CPU 时间。垃圾回收器将更有效地花费 CPU 时间来扫描堆的子集,在该子集中更有可能遇到作为删除候选者的对象。这种更有效的 CPU 时间使用可以用来减少暂停时间、提高吞吐量或减少内存使用量;这些方面的确切改进将取决于垃圾回收器的启发式算法及其配置方式。

如前所述,分代垃圾回收器中的内存堆被划分为空间。让我们更详细地了解这些代。

  • 年轻空间 - 年轻区域,顾名思义,是包含新分配对象的堆区域。年轻区域本身又进一步划分为更多区域。

    • 伊甸园空间 - 在分配时,对象存储在堆的伊甸园区域,直到其第一次垃圾回收。
    • 幸存者空间 - 在 GC 周期中幸存下来的对象将被复制到幸存者区域。分代回收器通常有多个“幸存者”区域;其目的是通过将幸存对象复制到新的幸存者区域,然后释放整个旧幸存者区域来提高垃圾回收效率。
  • 老年代 - 如果一个对象通过在 GC 周期中幸存下来获得了足够的“年龄”,它将被复制到老年代。如前所述,垃圾回收器很少扫描老年代以查找不再可达的对象。

  • 永久/元空间区域 - 最后一个区域是永久或元空间区域。存储在此处的对象通常是 JVM 元数据、核心系统类和其他通常在 JVM 整个生命周期中存在的数据。

 

垃圾回收过程

从高层次上讲,垃圾回收器有三个阶段:标记、清除和压缩。每个步骤都有不同的职责。但请注意,根据垃圾回收器的实现,每个阶段中可能还有其他子阶段,这里没有涵盖。

标记

在对象创建时,每个对象都会由 VM 提供一个 1 位标记值,最初设置为 false (0)。垃圾回收器使用此值来标记一个对象是否可达。在垃圾回收开始时,垃圾回收器遍历对象图并将它可以到达的任何对象标记为 true (1)。

垃圾回收器不会单独扫描每个对象,而是从“根”对象开始。根对象的示例包括:局部变量、静态类字段、活动 Java 线程和 JNI 引用。

清除

在清除阶段,所有不可达的对象,即其标记位当前为 false (0) 的对象,都会被删除。

压缩

垃圾回收的最后阶段是压缩阶段。伊甸园区域或已占用幸存者区域中的活动对象将被移动和/或复制到空的幸存者区域。如果幸存者区域中的对象获得了足够的任期,它将被移动或复制到老年代。

垃圾回收暂停

在垃圾回收期间,可能存在一些时期,JVM 中的一些甚至所有处理都会暂停,这些被称为“停止世界”事件。如堆内存部分的介绍中所述,存储在堆内存中的对象不是线程安全的。这意味着在垃圾回收期间,必须暂停 JVM 的一部分或全部,以便在垃圾回收器检查对象的使用情况、删除对象以及移动或复制对象时,防止发生错误。

JDK Flight Recorder (JFR) 和 Visual VM 等工具可用于监控垃圾回收导致的暂停的频率和持续时间。如何调整垃圾回收器超出了本教程的范围,但监控垃圾回收器的行为,然后通过 JVM 参数对其进行调整,可能是提高应用程序性能的关键方法。

垃圾回收的类型

就像堆内存有不同的区域一样,垃圾回收也有不同的类型。

  • 次要 - 次要垃圾回收只扫描堆内存的年轻区域。次要垃圾回收非常频繁发生,并且通常与它们相关的暂停时间非常短。
  • 主要 - 主要垃圾回收扫描堆内存的年轻区域和老年代。主要垃圾回收比次要垃圾回收发生的频率低得多,通常由 VM 中的特定条件触发,例如,当使用了一定比例的堆内存时。

更多学习


最后更新: 2022 年 3 月 6 日


系列中的上一篇
当前教程
Java 中的垃圾回收
系列中的下一篇

系列中的上一篇: 垃圾回收简介

系列中的下一篇: ZGC 概述