垃圾回收

2020/12/11 JVM

# 垃圾回收

# 垃圾回收 (Garbage Collection) 详解

# 1. 概述

垃圾回收(Garbage Collection,简称 GC)是 Java 虚拟机(JVM)中一项关键的自动内存管理机制。 它的主要作用是自动释放不再被程序使用的内存空间,从而避免内存泄漏和提高内存利用率。 GC 的目标是识别并回收那些“死亡”对象,即程序中不再引用的对象。

# 2. 为什么需要垃圾回收?

在没有垃圾回收机制的情况下,程序员需要手动分配和释放内存。 手动内存管理容易出错,可能导致以下问题:

  • 内存泄漏(Memory Leak):已分配的内存无法被释放,导致可用内存逐渐减少,最终耗尽系统资源。
  • 悬挂指针(Dangling Pointer):释放后的内存被再次访问,可能导致程序崩溃或数据损坏。

垃圾回收机制能够自动管理内存,大大降低了程序员的负担,提高了程序的稳定性和可靠性。

# 3. 垃圾回收的基本过程

垃圾回收的基本过程包括以下几个步骤:

  1. 标记(Marking):从根对象(Root Objects)开始,遍历所有可达对象,标记为“存活”对象。
  2. 清除(Sweeping):扫描整个堆内存,回收未被标记为“存活”的对象,即“死亡”对象。
  3. 整理(Compacting,可选):将存活对象移动到一起,整理内存空间,减少内存碎片。

# 4. 垃圾回收算法

常见的垃圾回收算法包括:

# 4.1 标记-清除算法(Mark and Sweep)

  • 过程
    1. 标记:从根对象开始,标记所有可达对象。
    2. 清除:扫描整个堆内存,回收未被标记的对象。
  • 优点:简单直接。
  • 缺点
    • 效率低:标记和清除两个过程都需要扫描整个堆内存。
    • 内存碎片:清除后会产生大量不连续的内存碎片,可能导致无法分配大对象。

# 4.2 复制算法(Copying)

  • 过程:将堆内存分为两个大小相等的区域,每次只使用其中一个区域。
    1. 标记:从根对象开始,标记所有可达对象。
    2. 复制:将存活对象复制到另一个未使用的区域。
    3. 清除:清除当前区域的所有对象。
    4. 区域切换:交换两个区域的角色,始终保证一个区域是空的。
  • 优点
    • 效率高:只需要复制存活对象,速度较快。
    • 无内存碎片:复制后内存空间是连续的。
  • 缺点
    • 空间利用率低:每次只能使用一半的内存空间。
    • 对象复制开销:需要复制存活对象,有一定开销。

# 4.3 标记-整理算法(Mark and Compact)

  • 过程
    1. 标记:从根对象开始,标记所有可达对象。
    2. 整理:将存活对象移动到内存的一端,使其紧凑排列。
    3. 清除:清除边界以外的所有对象。
  • 优点
    • 无内存碎片:整理后内存空间是连续的。
    • 空间利用率较高:不需要预留额外的内存空间。
  • 缺点
    • 效率较低:整理过程需要移动对象,开销较大。

# 4.4 分代收集算法(Generational Collection)

分代收集算法是目前 JVM 中最常用的垃圾回收算法。 它基于以下两个假设:

  • 大部分对象都是“朝生夕灭”的:即生命周期很短,很快就会被回收。
  • 熬过多次垃圾回收的对象往往生命周期较长

根据对象的生命周期,将堆内存分为不同的区域(代):

  • 新生代(Young Generation):存放新创建的对象,通常采用复制算法进行垃圾回收。 新生代又分为 Eden 区和两个 Survivor 区(S0 和 S1)。
  • 老年代(Old Generation):存放经过多次垃圾回收仍然存活的对象,通常采用标记-清除或标记-整理算法进行垃圾回收。
  • 永久代/元空间(Permanent Generation/Metaspace):存放类信息、常量、静态变量等数据。 在 JDK 8 之后,永久代被元空间取代,元空间使用的是本地内存。

分代收集算法根据不同代的特点,采用不同的垃圾回收策略,从而提高垃圾回收的效率。

# 5. 垃圾回收器

垃圾回收器是垃圾回收算法的具体实现。 JVM 提供了多种垃圾回收器,可以根据不同的应用场景选择合适的垃圾回收器。 常见的垃圾回收器包括:

  • Serial 收集器
    • 特点:单线程执行,会暂停所有用户线程(Stop-The-World,简称 STW)。
    • 适用场景:适用于单 CPU 或小内存的客户端应用。
  • Parallel 收集器
    • 特点:多线程并行执行,会暂停所有用户线程。
    • 适用场景:适用于多 CPU、对吞吐量有要求的服务器端应用。
  • CMS (Concurrent Mark Sweep) 收集器
    • 特点:并发执行,尽量减少暂停用户线程的时间。
    • 适用场景:适用于对响应时间有要求的服务器端应用。
    • 缺点
      • CPU 占用高:并发执行会占用一部分 CPU 资源。
      • 内存碎片:基于标记-清除算法,会产生内存碎片。
  • G1 (Garbage-First) 收集器
    • 特点:面向服务端应用,追求低停顿时间。
    • 适用场景:适用于大内存、对响应时间有较高要求的应用。
    • 优点
      • 可预测的停顿时间:通过可配置的停顿时间目标,满足应用的响应时间要求。
      • 并发执行:大部分工作与用户线程并发执行。
      • 空间整合:采用标记-整理算法,减少内存碎片。
  • ZGC (Z Garbage Collector):
    • 特点: 低延迟垃圾收集器, 主要目标是支持TB级别的堆,并且停顿时间控制在10ms以内.
    • 适用场景: 适用于需要低延迟和高吞吐量的应用, 比如金融交易系统.
    • 优点:
      • 极低的停顿时间: 停顿时间非常短, 几乎不会影响应用程序的响应.
      • 支持大堆: 能够有效地管理非常大的堆内存.
      • 并发执行: 大部分垃圾回收工作与应用程序并发执行.

# 6. 垃圾回收的触发时机

垃圾回收通常在以下情况下触发:

  • 堆内存不足:当堆内存即将耗尽时,JVM 会触发垃圾回收,尝试释放更多的内存空间。
  • 系统空闲时:JVM 可能会在系统空闲时执行垃圾回收,以优化内存使用。
  • 手动触发:可以通过 System.gc() 方法手动触发垃圾回收,但这只是建议 JVM 执行 GC,并不保证立即执行。

# 7. 如何优化垃圾回收?

优化垃圾回收的目标是减少 GC 的频率和停顿时间,从而提高程序的性能。 常见的优化手段包括:

  • 选择合适的垃圾回收器:根据应用场景选择合适的垃圾回收器。
  • 调整堆内存大小:合理设置堆内存的大小,避免频繁的 GC。
  • 减少对象创建:尽量重用对象,避免频繁创建和销毁对象。
  • 避免长时间持有对象引用:及时释放不再使用的对象引用,帮助 GC 回收内存。
  • 使用对象池:对于频繁创建和销毁的对象,可以使用对象池来提高性能。

# 8. 总结

垃圾回收是 JVM 中一项关键的自动内存管理机制,能够自动释放不再被程序使用的内存空间,避免内存泄漏和提高内存利用率。 深入理解垃圾回收的原理和优化手段,有助于我们编写更高性能的 Java 程序。

Last Updated: 2025/6/20 17:20:46