JVM工作原理与实战(二十六):堆的垃圾回收-垃圾回收器

前言

JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了垃圾回收器、Serial垃圾回收器、SerialOld垃圾回收器、ParNew垃圾回收器、CMS垃圾回收器、Parallel Scavenge垃圾回收器、Parallel Old垃圾回收器等内容。


一、垃圾回收器介绍

知识点回顾:

在Java等虚拟机中,垃圾回收(GC)是一个重要的内存管理机制。为了更有效地进行垃圾回收,分代GC算法将堆内存分为年轻代和老年代。这种划分的依据是对象的存活周期,大部分对象在创建后很快就不再使用,可以被回收。例如,用户获取订单数据,订单数据返回给用户之后就可以被释放。这种短暂存活的对象被放置在年轻代中。另一方面,老年代中存放的是长期存活的对象。例如,Spring框架中的大部分bean对象,在程序启动后就不会被回收。这些对象由于存活时间长,通常较大且稳定,因此被放置在老年代中。

将堆分成年轻代和老年代的主要原因是:

  • 灵活性:通过调整年轻代和老年代的比例,可以更好地适应不同类型的应用程序,从而提高内存的利用率和性能。
  • 算法选择:新生代和老年代使用不同的垃圾回收算法。新生代通常采用复制算法,而老年代可以选择标记-清除或标记-整理算法。这种灵活性为程序员提供了更多选择。
  • 减少STW时间:分代设计允许仅回收新生代(minor gc),如果满足对象分配要求,就不需要对整个堆进行回收(full gc)。这有助于减少STW时间,提高应用的响应性。

垃圾回收器是Java虚拟机(JVM)中的重要组件,负责自动管理内存,回收不再使用的对象所占用的空间。了解垃圾回收器的种类、工作原理以及如何根据应用场景选择合适的垃圾回收器,对于提高应用程序的性能和稳定性至关重要。

垃圾回收器通过自动检测和回收不再被引用的对象,以释放内存空间,避免内存泄漏。为了实现这一目标,垃圾回收器采用了一系列算法来识别和回收无用对象。这些算法主要包括标记清除复制标记整理分代垃圾回收等。

详细讲解可以查看之前的文章:

JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法 - 掘金 (juejin.cn)

垃圾回收器分为年轻代和老年代,它们各自负责不同生命周期的对象的回收。除了G1垃圾回收器外,其他垃圾回收器必须成对组合使用,以确保整个堆内存的有效管理。

二、主要的垃圾回收器

1.年轻代-Serial垃圾回收器

年轻代-Serial垃圾回收器是一种针对年轻代 内存区域进行管理的单线程串行回收器 。它采用复制算法,将活跃对象从一个内存区域复制到另一个内存区域,从而实现内存空间的回收。

Serial垃圾回收器在单CPU处理器环境下表现非常出色,具有较高的吞吐量。它能够有效地管理年轻代内存区域,确保垃圾回收过程的高效执行。由于其单线程特性,Serial垃圾回收器在多CPU环境下可能无法充分利用系统资源,导致吞吐量下降。此外,如果堆内存大小配置不当,可能会导致用户线程长时间的等待,影响程序的性能。

适用场景: Serial垃圾回收器适用于Java编写的客户端程序或者硬件配置有限的场景。对于需要快速响应的应用程序,Serial垃圾回收器可能是一个不错的选择,因为它能够提供较快的垃圾回收速度,从而减少应用程序的停顿时间。此外,在硬件资源有限的环境下,Serial垃圾回收器也能够提供较好的性能表现。

2.老年代-SerialOld垃圾回收器

老年代-SerialOld垃圾回收器是Serial垃圾回收器在老年代 内存管理领域的特定实现。它采用单线程串行回收 方式,确保老年代内存区域的稳定和高效管理。SerialOld垃圾回收器主要采用标记-整理算法,针对老年代内存区域进行垃圾回收。该算法通过标记无用对象并进行整理,有效地回收内存空间并保持内存区域的稳定。

SerialOld垃圾回收器在单CPU处理器环境下表现出色,具有较高的吞吐量。它可以有效地管理老年代内存区域,确保垃圾回收过程的高效执行。然而,在多CPU环境下,由于其单线程特性,性能可能会受到限制,吞吐量不如其他并行垃圾回收器。此外,如果堆内存大小配置不当,可能会导致用户线程长时间的等待,影响程序的性能。

适用场景: SerialOld垃圾回收器通常与Serial垃圾回收器成对使用,以提供完整的年轻代和老年代管理方案。通过使用**-XX:+UseSerialGC**参数,可以确保新生代和老年代都使用串行回收器进行垃圾回收。在某些特殊情况下,如在CMS垃圾回收器的特殊情况下,也可能需要使用SerialOld来确保老年代内存的有效管理。

ruby 复制代码
-XX:+UseSerialGC

3.年轻代-ParNew垃圾回收器

年轻代-ParNew垃圾回收器是一种针对年轻代 内存区域进行管理的垃圾回收器,它在多CPU环境下对Serial垃圾回收器进行了优化。ParNew垃圾回收器采用多线程 技术,利用多个处理器核心同时进行垃圾回收操作,以提高垃圾回收的并行性和吞吐量。ParNew垃圾回收器主要采用复制算法,将活跃对象从一个内存区域复制到另一个内存区域,从而实现内存空间的回收。

在多CPU环境下,由于其多线程特性,ParNew垃圾回收器能够有效地降低停顿时间,提高系统的响应能力。ParNew垃圾回收器也存在一些缺点,与G1垃圾回收器相比,ParNew在吞吐量和停顿时间方面可能存在一定的不足。因此,在JDK9及之后的版本中,官方已经不建议使用ParNew垃圾回收器。

适用场景: 年轻代-ParNew垃圾回收器主要适用于JDK8及之前的版本。通过使用**-XX:+UseParNewGC**参数,可以指定新生代使用ParNew回收器,老年代则使用串行回收器。在JDK8及之前的版本中,ParNew垃圾回收器通常与CMS老年代垃圾回收器配合使用,提供全面的年轻代和老年代内存管理方案。在某些特定的应用场景下,对于需要较低停顿时间和较高吞吐量的应用程序,ParNew垃圾回收器可能是一个不错的选择。

ruby 复制代码
-XX:+UseParNewGC

4.老年代- CMS(Concurrent Mark Sweep)垃圾回收器

老年代- CMS垃圾回收器是一种关注系统暂停时间 的垃圾回收器,主要针对老年代 内存区域进行管理,允许用户线程和垃圾回收线程在某些步骤中同时执行。它采用标记清除算法,旨在减少用户线程的等待时间,提高系统响应能力。

CMS垃圾回收器的执行步骤包括:

  1. 初始标记:这一阶段使用极短的时间标记出GC Roots直接关联的对象,以确定垃圾回收的起始点。
  2. 并发标记:在这一阶段,垃圾回收线程与用户线程同时执行,标记所有存活的对象。这样可以在不影响用户线程执行的情况下完成对象标记工作。
  3. 重新标记:由于并发标记阶段可能存在对象变化的情况,需要进行重新标记,以确保准确的垃圾回收。
  4. 并发清理:清理已经标记为死亡的对象,同时用户线程仍可以继续执行。

CMS垃圾回收器的主要优势在于其关注系统暂停时间,通过并发执行和减少用户线程等待时间来提高用户体验。此外,CMS还具有内存碎片少的特点,能够提供更为连续的内存空间。然而,CMS垃圾回收器也存在一些缺点和问题:

  • 内存碎片问题:CMS使用标记清除算法,会在垃圾收集后产生大量的内存碎片。虽然CMS会在Full GC时进行碎片整理,但整理过程会导致用户线程暂停,影响系统性能。可以通过调整参数-XX:CMSFullGCsBeforeCompaction(默认0)来控制碎片整理的频率(N次Full GC后再整理)。
  • 无法处理浮动垃圾:在并发清理过程中可能产生"浮动垃圾",这些垃圾无法被完全回收,导致内存空间的浪费。
  • 退化问题:当老年代内存不足无法分配对象时,CMS会退化成Serial Old单线程回收老年代,这可能导致性能下降。
  • 线程资源争抢问题:在并发阶段运行时的线程数是由系统计算得出的,如果CPU核数有限,可能会影响用户线程执行的性能。可以通过调整参数-XX:ConcGCThreads(默认0)来控制并发阶段运行的线程数。计算公式为(-XX:ParallelGCThreads定义的线程数 + 3) / 4, ParallelGCThreads是STW停顿之后的并行线程数,ParallelGCThreads是由处理器核数决定的(当cpu核数小于8时,ParallelGCThreads = CPU核数,否则 ParallelGCThreads = 8 + (CPU核数 -- 8 )*5/8 )。

适用场景: 老年代- CMS垃圾回收器适用于大型的互联网系统中用户请求数据量大、频率高的场景。通过使用-XX:+UseConcMarkSweepGC参数,可以启用CMS垃圾回收器。在这些场景下,系统对响应时间要求较高,而CMS垃圾回收器能够通过减少用户线程等待时间来提高系统响应能力。

ruby 复制代码
-XX:+UseConcMarkSweepGC

5.年轻代-Parallel Scavenge垃圾回收器

Parallel Scavenge是JDK8默认的年轻代垃圾回收器,专注于提升系统的吞吐量。它采用多线程 并行回收的方式,旨在最大限度地提高应用程序在垃圾回收过程中的执行效率。Parallel Scavenge具备自动调整堆内存大小的特点,可以根据应用程序的需求动态调整内存分配,以获得更好的性能。Parallel Scavenge主要针对年轻代 进行管理,采用复制算法进行垃圾回收。在多线程并行回收的机制下,Parallel Scavenge能够显著提高垃圾回收的效率,从而提高系统的吞吐量。

Parallel Scavenge的优点在于其高吞吐量以及手动可控的特点。为了提高吞吐量,虚拟机会动态调整堆的参数,以满足应用程序的性能需求。这种自动调整内存大小的功能有助于提高系统的适应性和性能。Parallel Scavenge也存在一些缺点,由于其关注的是系统的吞吐量,而不是单次的停顿时间,因此可能会在某些情况下导致较长的停顿时间。

适用场景: Parallel Scavenge主要适用于后台任务,这些任务不需要与用户频繁交互,并且容易产生大量的对象。例如,大数据的处理、大文件的导出等任务就非常适合使用Parallel Scavenge垃圾回收器。在这些场景下,Parallel Scavenge能够通过提高系统吞吐量来提高任务的执行效率。

Parallel Scavenge允许手动设置最大暂停时间和吞吐量。Oracle官方建议在使用这个组合时,不要设置堆内存的最大值,垃圾回收器会根据最大暂停时间和吞吐量自动调整内存大小。

  • 最大暂停时间:-XX:MaxGCPauseMillis=n设置每次垃圾回收时的最大停顿毫秒数。
  • 吞吐量:-XX:GCTimeRatio=n设置吞吐量为n(用户线程执行时间 = n/n + 1)。
  • 自动调整内存大小:-XX:+UseAdaptiveSizePolicy设置可以让垃圾回收器根据吞吐量和最大停顿的毫秒数自动调整内存大小。
ruby 复制代码
-XX:MaxGCPauseMillis=n
-XX:GCTimeRatio=n
-XX:+UseAdaptiveSizePolicy

6.老年代-Parallel Old垃圾回收器

Parallel Old垃圾回收器是为Parallel Scavenge收集器设计的老年代版本,专门用于管理老年代 内存区域。它利用多线程 并发收集技术,旨在提高老年代垃圾回收的效率和吞吐量。Parallel Old采用标记-整理算法对老年代进行管理。在多线程并发收集的机制下,Parallel Old能够充分利用多核CPU的计算能力,提高垃圾回收的效率。

Parallel Old的优点在于其并发收集的特性,使得在多核CPU环境下表现出较高的效率。通过并发收集,Parallel Old能够降低垃圾回收对应用程序的影响,提高系统的吞吐量。Parallel Old也存在一些缺点,由于其采用标记-整理算法,垃圾回收过程中可能需要暂停用户线程,导致较长的停顿时间。此外,Parallel Old可能不太适合内存容量非常大的系统,因为在大内存环境下,垃圾回收的开销可能会增加。

适用场景: Parallel Old通常与Parallel Scavenge收集器配套使用。通过使用**-XX:+UseParallelGC-XX:+UseParallelOldGC**参数,可以启用Parallel Scavenge和Parallel Old的组合。这种组合适用于后台任务和大数据处理的场景,其中应用程序可以容忍较长的停顿时间,并希望通过提高吞吐量来提高任务的执行效率。通过使用Parallel Scavenge和Parallel Old的组合,应用程序可以在多核CPU环境下获得更好的性能表现。

ruby 复制代码
-XX:+UseParallelGC
-XX:+UseParallelOldGC

总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了垃圾回收器、Serial垃圾回收器、SerialOld垃圾回收器、ParNew垃圾回收器、CMS垃圾回收器、Parallel Scavenge垃圾回收器、Parallel Old垃圾回收器等内容,下一节将会介绍G1垃圾回收器,希望对大家有所帮助。

相关推荐
苍何28 分钟前
30分钟用 Agent 搓出一家跨境网店,疯了
后端
ssshooter42 分钟前
Tauri 2 iOS 开发避坑指南:文件保存、Dialog 和 Documents 目录的那些坑
前端·后端·ios
追逐时光者1 小时前
一个基于 .NET Core + Vue3 构建的开源全栈平台 Admin 系统
后端·.net
程序员飞哥1 小时前
90后大龄程序员失业4个月终于上岸了
后端·面试·程序员
zs宝来了2 小时前
Playwright 自动发布 CSDN 的完整实践
java
吴声子夜歌3 小时前
TypeScript——基础类型(三)
java·linux·typescript
GetcharZp3 小时前
Git 命令行太痛苦?这款 75k Star 的神级工具,让你告别“合并冲突”恐惧症!
后端
庞轩px4 小时前
模拟面试回答第十三问:JVM内存模型
jvm·面试·职场和发展
Victor3564 小时前
MongoDB(69)如何进行增量备份?
后端