Last Updated:

Java 垃圾收集的阶段和级别

银狐

垃圾收集是高级语言的一大好处。垃圾收集使程序员免于进行大量内务处理,并且有助于防止因内务管理而可能出现的非常严重的错误。但是,垃圾收集是由在不可预测的时间运行的后台线程执行的,并且可以强制应用程序减慢或暂停。

生产应用程序性能的最重要方面之一是选择正确的垃圾收集器并对其进行优化配置。最佳选择取决于每个应用程序的行为和要求。因此,每个Java开发人员都应该了解并遵循推荐的垃圾收集最佳实践。

本文是有关 Java 中垃圾收集的四部分系列文章的开头。这篇文章描述了垃圾收集的过程和不同级别,并提供了几种查看实际垃圾收集的方法。后续文章将深入探讨更多细节,帮助您选择垃圾收集器,并向您展示如何跟踪其效果。

到目前为止阅读该系列

第 1 部分:垃圾收集的阶段和级别

第 2 部分:JVM 如何使用和分配内存

内存管理和避免内存泄漏

内存管理是分配新对象并在不再需要时释放或删除对象的过程。在C 和 C++ 中,应用程序程序员必须通过在对象被使用后释放对象来手动确保内存管理。这项手动任务会导致应用程序内存泄漏的高风险,因为开发人员可能会在使用后忘记删除对象。当应用程序遭受内存泄漏时,其内存消耗会不断增加,并且在某个时刻应用程序可能无法获得用于分配新对象的可用内存。此时,应用程序会因内存不足错误而失败。一个更严重的错误是引用程序已经释放的对象;这可能会导致立即崩溃。

相比之下,Java 等语言运行自动垃圾收集器,在使用后将每个对象从内存中删除。应用程序创建的 Java 对象驻留在称为的内存段中。当程序创建新对象并且堆已满时,Java 虚拟机 (JVM) 会触发垃圾回收。

基本垃圾收集:标记、清除、压缩

垃圾回收的三个基本步骤:

  1. 标记:垃圾收集器扫描堆内存段并标记所有活动对象——即应用程序持有引用的对象。所有没有引用它们的对象都有资格被移除。
  2. Sweep:垃圾收集器从堆中回收所有未引用的对象。
  3. Compact:sweep step往往会在堆内存中留下很多空区域,造成内存碎片。因此,compact 阶段有助于将对象排列到堆开始处的连续块中。这反过来有助于按顺序分配新对象。

然而,垃圾收集比仅仅运行这些步骤更复杂,因为大多数对象都是短暂的。在堆上的所有对象上频繁运行标记和压缩步骤将是低效和耗时的。所以,让我们看一个更复杂的垃圾收集算法。

分代垃圾收集

为了有效地处理生命周期较短的对象,我们扩展了我刚刚描述的简单垃圾收集序列,为存在不同时间长度的对象添加了不同的级别。这种算法称为分代垃圾收集

生成的垃圾收集分歧堆内存分为两个主要的分区中,年轻的(也称为苗圃)的产生和(也叫终身教授)的生成。还有几种类型的垃圾收集:年轻代的次要收集,老年代的主要收集,以及同时进行次要和主要收集以及压缩的完整收集。

年轻的对象

所有新对象最初都分配给年轻代,年轻代又细分为两个分区:Edensurvivor。Eden 分区是放置所有新对象的地方。在每个垃圾回收周期之后,所有留在 Eden 分区中的对象都被移动到 Survivor 分区。

幸存者分区进一步分为两个分区,称为 S0 和 S1,也称为 FromSpace 和 ToSpace。

对象分配的正常流程是:

  1. 首先,所有的新对象都分配在 Eden 分区中,而两个 Survivor 分区都是空的。
  2. 当 Eden 分区已满,导致新分配失败时,JVM 会启动一个小垃圾收集。不再需要的对象被移除后,所有存活的对象都会被标记并移动到 S0 分区。因此,Eden 分区被清除,而 S1 仍然是空的。
  3. 下一次 Eden 填满并发生另一次小的垃圾收集时,它会标记 Eden 和 S0 分区中的所有活动对象。然后所有来自 Eden 和 S0 的活动对象都被移动到 S1 中。Eden 和 S0 留空。在任何给定时间,幸存者分区之一(S0 或 S1)始终为空。
  4. 下一个次要垃圾收集执行与步骤 3 中相同的过程,但将对象从 S1 移至 S0,而不是从 S0 移至 S1。所有活着的对象都留在 S0 中。

按照刚才描述的顺序,次要垃圾回收在第 3 步和第 4 步之间交替进行。经过多次次要垃圾回收,当对象在年轻代中长期存在时,它们就有资格升级到老年代。

旧物

老年代的垃圾回收称为主要垃圾回收,执行标记和清除。

完整的垃圾收集会清理年轻代和年老代。它将所有活着的对象从年轻代提升到老年代并压缩老年代。完全垃圾收集会导致应用程序停止世界暂停,以确保在堆内存上没有分配新对象,并且在执行完全垃圾收集时没有现有对象变得不可访问。

尽管垃圾收集在 Java 中自动发生,但可以明确要求 JVM 使用System.gc()Runtime.gc()方法进行垃圾收集。但是,这些方法并不能保证垃圾收集;它完全依赖于JVM。不鼓励使用这些方法请求垃圾回收。

监控垃圾收集活动和堆使用

有多种方法可以监控垃圾回收活动和堆内存使用。

垃圾收集日志

垃圾收集日志通常有助于解决与内存相关的性能问题。这些日志不会给服务器带来很大的开销,因此建议在生产环境中启用它们以进行调试。文档如何启用 Java 垃圾收集日志记录?解释如何启用垃圾收集日志。

使用 jstat 监控垃圾收集

jstat命令行实用程序可以用来监控垃圾收集活动,如堆内存使用,元空间中使用,次要的,主要的号码,并充分垃圾收集事件,和垃圾收集时间。运行以下命令,替换$JAVA_PID为您的应用程序的进程 ID:

jstat -gc $JAVA_PID

图 1 说明了输出。

jstat 命令显示有关正在运行的 Java 应用程序的统计信息。
图 1:jstat 命令的输出。

使用 jconsole 用户界面监控垃圾收集

JConsole的命令启动了图形界面,运行Java应用程序的显示方面。您可以在那里监控内存使用、线程使用和 CPU 使用,如图 2 所示。

控制台显示内存、线程、类和 CPU 使用等统计信息。
图 2:jconsole 的用户界面。

您还可以从 触发垃圾回收jconsole,如图 3 所示。

jconsole 用户界面提供了一个“Perform GC”按钮来触发垃圾回收。
图 3:按钮在 jconsole 中触发垃圾回收。

结论

Java 中的内存管理是一个自动过程,有助于分配对象并在使用后释放它们。分代垃圾收集根据对象的生命周期对对象进行分类,并相应地将对象分配给不同的代。许多工具可让您监控堆使用和垃圾收集。

查找本系列下一篇文章,其中介绍了 JVM 中的内存使用及其与垃圾收集的关系。

最后更新:2021 年 9 月 9 日

来自:https://developers.redhat.com/articles/2021/08/20/stages-and-levels-java-garbage-collection