Java垃圾回收机制 | AI生成和翻译
好的,我们来学习 Java 中的垃圾回收!这是 Java 内存管理的一个关键方面,它帮助开发者避免手动内存分配和释放。
什么是 Java 中的垃圾回收?
垃圾回收是自动回收 Java 应用程序不再使用的对象所占用的内存的过程。本质上,这是 Java 清理”垃圾”以腾出空间给新对象的方式。
为什么需要垃圾回收?
在许多其他编程语言中,开发者负责使用 malloc 和 free 等函数显式分配和释放内存。这种手动内存管理可能导致几个问题:
- 内存泄漏: 如果内存被分配但从未释放,应用程序最终可能因内存不足而崩溃。
- 悬垂指针: 如果内存被释放,但指针仍然指向该内存位置,使用该指针可能导致不可预测的行为或崩溃。
- 增加开发复杂性: 手动管理内存给软件开发增加了显著的复杂性。
Java 的垃圾回收自动化了这个过程,将开发者从手动内存管理的负担中解放出来,使得编写更安全、更可靠的代码更加容易。
垃圾回收如何工作?
垃圾回收背后的核心思想是识别内存中哪些对象仍被应用程序使用,哪些没有。然后,垃圾回收器回收未被使用的对象所占用的内存。
以下是该过程的简化概述:
- 识别活动对象: 垃圾回收器首先识别从”根”对象仍然可达的对象集合。根对象通常是应用程序可以直接访问的对象,例如:
- 当前执行方法中的局部变量。
- 静态变量。
- 被本地代码引用的对象。
- Java 虚拟机的活动线程。
垃圾回收器从这些根开始遍历对象图,标记所有可达的对象。
-
回收内存: 一旦活动对象被标记,垃圾回收器需要回收未被标记的对象所占用的内存。不同的垃圾回收算法对此采用不同的策略:
- 标记-清除: 此算法识别并标记活动对象,然后遍历内存,释放未被标记对象占用的空间。这可能导致内存碎片。
- 标记-整理: 此算法也标记活动对象。标记之后,它将活动对象移动到一起,从而消除碎片,使得为新建对象分配连续的内存块更加容易。
- 复制: 此算法将内存分为两个或多个区域。活动对象从一个区域复制到另一个区域,从而有效地回收原始区域的空间。
Java 垃圾回收的关键概念:
- 堆: Java 中对象被分配的内存区域。垃圾回收器主要操作堆内存。
- 年轻代: 这是堆的一部分,新创建的对象最初分配在这里。它进一步分为:
- 伊甸园区: 大多数新对象创建的地方。
- 幸存者区: 用于存放经历过几次 Minor GC 后仍然存活的对象。
- 老年代: 在年轻代中经历过多次垃圾回收周期后仍然存活的对象最终会被移动到老年代。老年代中的对象通常是长寿命的。
- 永久代 / 元空间: 在旧版本的 Java 中,永久代存储有关类和方法的元数据。在 Java 8 及更高版本中,这已被元空间取代,元空间是本地内存的一部分。
- 垃圾回收算法: 不同的算法用于垃圾回收,每种算法在性能和效率方面都有其自身的权衡。
分代垃圾回收:
Java HotSpot JVM 使用分代方法进行垃圾回收。这是基于观察到应用程序中的大多数对象寿命很短。
-
Minor GC: 当伊甸园区变满时,会触发一次 Minor GC。来自伊甸园区和一个幸存者区的存活对象被复制到另一个幸存者区。经历过一定次数 Minor GC 周期后仍然存活的对象会被移动到老年代。不可达的对象被丢弃。
-
Major GC / Full GC: 当老年代变满时,会执行一次 Major GC。这个过程通常比 Minor GC 更耗时,并可能导致应用程序执行出现较长的暂停。
Java HotSpot JVM 中常见的垃圾回收器:
Java HotSpot JVM 提供了几种垃圾回收算法,可以根据应用程序的要求进行选择。一些常见的包括:
- 串行回收器: 使用单线程进行垃圾回收。适用于资源有限的小型应用程序。
- 并行回收器: 使用多线程进行垃圾回收,提高了吞吐量。适用于在多核处理器上运行的中等到大型数据集的应用程序。
- CMS 回收器: 尝试通过与应用程序线程并发执行大部分垃圾回收工作来最小化暂停时间。然而,它可能导致碎片,并且最终可能需要一次 Full GC。
- G1 回收器: 旨在提供吞吐量和低延迟之间的良好平衡。它将堆划分为多个区域,并优先从垃圾最多的区域收集垃圾。它是 Java 9 及更高版本中的默认回收器。
- ZGC: 一种为大型堆设计的低延迟垃圾回收器。它旨在实现低于 10ms 的暂停时间。
- Shenandoah: 另一种低延迟垃圾回收器,目标与 ZGC 类似。
您可以通过 JVM 命令行选项指定使用哪个垃圾回收器。
垃圾回收何时运行?
垃圾回收主要是一个由 JVM 驱动的自动过程。它通常在以下情况下运行:
- 年轻代变满。
- 老年代变满。
- 系统内存不足。
虽然您不能直接控制垃圾回收何时运行,但您可以使用 System.gc() 向 JVM 建议现在可能是执行垃圾回收的好时机。但是,不能保证 JVM 在您调用此方法时会立即或完全运行垃圾回收器。通常最好依赖 JVM 的自动垃圾回收机制。
System.gc() 和终结:
System.gc(): 如前所述,这是向 JVM 发出的运行垃圾回收器的请求。通常建议避免依赖此方法进行关键的内存管理,因为 JVM 通常更擅长决定何时执行 GC。finalize()方法: 在对象被垃圾回收之前,JVM 通过调用其finalize()方法来给予它执行任何清理操作的机会。然而,finalize()有几个缺点,在现代 Java 开发中通常不鼓励使用。它可能引入性能问题并使垃圾回收不那么可预测。考虑使用其他机制,如 try-with-resources 进行资源管理。
垃圾回收对应用程序性能的影响:
虽然垃圾回收对于内存管理至关重要,但由于”Stop-The-World”暂停,它也会影响应用程序的性能。在这些暂停期间,所有应用程序线程都会停止,而垃圾回收器执行其工作。这些暂停的持续时间和频率取决于所使用的垃圾回收算法以及堆的大小和特性。
像 G1、ZGC 和 Shenandoah 这样的低延迟垃圾回收器旨在最小化这些暂停时间,使应用程序响应更迅速。
调优垃圾回收:
对于具有特定性能要求的应用程序,可以通过调整 JVM 参数来调优垃圾回收,例如:
- 堆大小
- 年轻代大小
- 幸存者区比例
- 选择特定的垃圾回收器
- 设置垃圾回收标志以进行更细粒度的控制。
GC 调优是一个高级主题,通常需要仔细分析应用程序的行为和性能。
编写对垃圾回收友好的代码的最佳实践:
虽然 Java 自动处理内存管理,但编写时考虑到垃圾回收可以带来更好的性能:
- 最小化对象创建: 创建大量短寿命对象会给垃圾回收器带来压力。在适当时尝试重用对象。
- 释放对象引用: 如果您有一个不再需要的对象引用,请将该引用设置为
null。这使对象有资格更快地被垃圾回收。 - 明智地使用数据结构: 选择适合您需求的数据结构。例如,使用一个大的
ArrayList然后丢弃其大部分元素可能是低效的。 - 避免终结器: 如前所述,避免依赖
finalize()方法进行资源清理。对文件和网络连接等资源使用 try-with-resources。 - 注意静态变量: 被静态变量引用的对象将在应用程序的整个持续时间内存活,因此要谨慎在静态字段中存储大型或不必要的对象。
总结:
Java 中的垃圾回收是一个自动内存管理过程,它回收未被使用的对象占用的内存。它通过将开发者从手动内存管理中解放出来而简化了开发。了解它的工作原理、堆的不同代以及可用的垃圾回收算法可以帮助您编写更高效、性能更好的 Java 应用程序。