最适合网络开发者的网站
Java 编程语言。hyperskill 为初学者提供的课程

尿素

垃圾收集器


理论

您已经知道,当 JVM 语言程序创建新对象时,会使用一部分堆内存。当没有可用的堆内存时会发生什么?JVM 如何知道哪些内存部分可以重新用于其他用途?在本主题中,我们将解决这些问题并了解垃圾收集 - 一种执行自动内存管理的方法。

§1. 内存管理策略

堆内存即使足够大,可以完成大多数编程任务,但仍然有限,因为它占用了计算机上物理内存 (RAM) 的一部分。因此,占用过多内存的程序最终会导致崩溃。大多数程序都有一些对象,在执行的某个阶段将不再使用,这意味着内存可能会被释放并在以后重新使用。

某些编程语言(例如 C 或 C++)要求程序员手动分配和释放内存。虽然这提供了对资源的更多控制,但 手动内存管理 可能是一项具有挑战性的任务,特别是对于初学者来说,会导致错误和内存泄漏。

JVM 利用 自动内存管理,这使得开发人员在编写代码时无需担心内存问题,并可防止可能出现的编程错误。每次程序创建新对象时,都会在 JVM 堆中分配内存,并使用垃圾回收过程释放内存。

垃圾收集者杜克

§2. 什么是垃圾收集?

垃圾收集器 (或者GC) 是 JVM 的一部分,用于在运行时释放内存以供进一步使用。垃圾收集算法和实现有很多种,但它们的工作可以简化为两个通用步骤。第一步是确定程序不再使用哪些内存部分(即“垃圾”),第二步是释放这些内存部分。此外,在删除步骤之后可以执行压缩操作 — 所有当前使用的对象都彼此相邻移动,以释放一个大的连续内存区域并避免碎片化。

为了识别垃圾,JVM 会遍历所有对象并检查它们在程序中是否仍可访问。所有无法从程序或其他可访问对象访问的对象都被视为“垃圾”,并释放相应的内存。

有些算法会考虑对象的附加信息,例如,自对象创建以来的时间。这样的算法称为 代际垃圾收集 算法。人们注意到,程序中的大多数对象在创建后只使用很短的时间。因此,垃圾收集器不需要在每次运行时检查堆中的每个对象,而主要关注最近创建的对象(“年轻一代”),从而缩短了垃圾收集时间。


§3. 运行 GC

垃圾收集在程序运行时自动执行。JVM 自行处理所有脏活,包括决定何时运行 GC。例如,它可能在固定时间间隔内或没有剩余可用堆内存时发生。

在大多数情况下,开发人员不需要考虑垃圾收集器如何工作以及如何对其进行自定义。然而,在现代高负载应用程序中,这些知识可能会很有用。

在您的程序中,您可以使用以下方式请求 GC 执行该作业:

  • 呼叫 系统.gc();
  • 呼叫 运行时.getRuntime().gc().

程序员不应该手动运行垃圾收集器,这些调用甚至不能保证 GC 调用。我们强烈建议仅在测试环境中使用它们。


§4. 示例

假设我们有一个代码来检查已使用的内存大小(你可以使用 运行时.getRuntime().totalMemory()运行时.getRuntime().freeMemory()),然后我们把它放入一个程序中,它的运行方式如下:

  1. 在执行任何操作之前打印使用的内存信息。
  2. 循环创建一堆新对象以供进一步使用。
  3. 打印对象创建后使用的内存信息。
  4. 执行必要的操作(不创建新的对象),以便代码中不再使用这些对象。

步骤 3 中的值将大于步骤 1 中的值,因为每个新对象都会占用一部分可用内存。在步骤 4 之后,对象将无法从代码中访问,可以通过调用释放内存 系统.gc() 或者运行时.getRuntime().gc()如果确实执行了垃圾收集,则在垃圾收集器调用后打印已用内存信息将显示一个小于步骤 3 中的值的值。


§5. 结论

  • JVM 执行自动内存管理,保护程序员免受可能的内存泄漏和错误的影响。
  • 垃圾收集器 (GC) 是 JVM 的一部分,它识别未使用的“垃圾”对象并清理相应的内存区域以便以后重新使用它们。
  • 可以在代码中手动调用 GC,但不能保证执行垃圾收集,因为决定权仍然在 JVM 手中。

您还可以查看 hyperskill.org 上的课程.


实践任务及答案

给出了任务和答案选项。正确选项以 蓝色的 颜色。

№1. 使用运行时调用 GC

问题: 选择描述下面这行代码对垃圾收集器所起的作用的语句:

运行时.getRuntime().gc()

从列表中选择一个选项:

  • 它不会影响 GC 的行为
  • 强制立即执行 GC
  • 请求执行 GC,但不一定会发生✔
  • 强制 GC 延迟一秒执行

解释。 语句“Runtime.getRuntime().gc()”请求执行 GC,但是并不一定会发生。


№2. 我们为什么需要它

问题: 垃圾收集过程的目的是什么?

从列表中选择一个选项:

  • 减少所需变量的数量
  • 创建新对象
  • 清理内存中未使用的对象✔
  • 终止程序

解释。 垃圾收集过程的目的是清除内存中未使用的对象。


№3. 使用运行时获取已用内存

问题: 如何通过以下调用获取当前使用的内存大小(以字节为单位)?

  1. 运行时.getRuntime().totalMemory(),返回已创建对象和未来对象可用的内存量(以字节为单位)。
  2. 运行时.getRuntime().freeMemory(),返回可用于未来对象的内存量(以字节为单位)。

从列表中选择一个选项:

  • Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() ✔
  • (运行时.getRuntime().totalMemory() + 运行时.getRuntime().freeMemory())/ 1024
  • 运行时.getRuntime().freeMemory() - 运行时.getRuntime().totalMemory()
  • 运行时.getRuntime().totalMemory() / 1024

解释。 可以通过从可用的总内存中减去可用内存量来获得当前使用的内存大小(以字节为单位),如下所示:Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()。


№4. 节省资源

问题: 垃圾收集器保存了什么?

从列表中选择一个选项:

  • 中央处理器
  • 磁盘内存
  • 网络流量
  • 内存 ✔

解释。 垃圾收集器通过释放未使用的对象占用的内存来节省 RAM(随机存取存储器)。


№5. 可以收集什么

问题: 考虑执行以下操作的代码:

  1. 创建一个字符串数组 bookNames 和一个整数数组 bookPages,长度均为 1000。
  2. 创建 1000 个 Book 类对象,其字符串成员 bookInfo 由相同索引的 bookNames 和 bookPages 值组成。
  3. 打印所有已创建的书籍对象的 bookInfo。

上述每个步骤之后,垃圾收集过程中可以清理什么?假设程序在步骤 1 之前启动,并在步骤 3 之后终止,而不执行任何其他操作。

匹配左列和右列的项目:

  • 步骤 1 之后 - 未出现任何提及的对象
  • 第 2 步之后 - bookNames 和 bookPages 数组
  • 步骤 3 之后 - Book 类的对象

解释。 1. 创建时间(无销毁和驱逐);2. 小部分的 GC 时间;3. 整个对象的 GC 时间。


№6. GC 行为

问题: 选择一个关于垃圾收集器的正确陈述。

从列表中选择一个选项:

  • 程序员应该在停止程序之前手动调用 GC,以避免内存泄漏
  • 仅当没有可用内存时,GC 才会清理内存
  • GC 仅在程序终止前自动清理内存
  • 程序运行时 GC 自动工作✔

解释。 程序运行时,GC 自动工作。


№7. 调用 GC

问题: 如何请求垃圾收集器进行清理?

从列表中选择一个或多个选项:

  • System.out.println("需要 GC")
  • GC.clean()
  • Runtime.getRuntime().gc() ✔
  • System.gc() ✔

解释。 您可以使用 Runtime.getRuntime().gc() 或 System.gc() 请求垃圾收集器执行清理。


什么是垃圾收集?

垃圾收集是 Java 虚拟机 (JVM) 自动释放程序中对象不再使用的内存的过程。当 Java 程序创建对象时,JVM 会分配内存来存储这些对象。但是,当不再需要某个对象时,它就有资格进行垃圾收集。垃圾收集器会识别并删除这些未使用的对象,释放内存供其他对象使用。此过程有助于防止内存泄漏,并通过减少程序本身需要管理的内存量来提高程序的性能。在 Java 中,开发人员无需手动释放内存,因为 JVM 会通过自动垃圾收集来处理它。