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

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

一、垃圾回收器介绍

知识点回顾:

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

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

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

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


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


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

image.gif

二、主要的垃圾回收器

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

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

image.gif

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


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

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

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

image.gif

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

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

-XX:+UseSerialGC

image.gif

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

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

image.gif

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

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

-XX:+UseParNewGC

image.gif

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

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

image.gif

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垃圾回收器能够通过减少用户线程等待时间来提高系统响应能力。

-XX:+UseConcMarkSweepGC

image.gif

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

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

image.gif

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设置可以让垃圾回收器根据吞吐量和最大停顿的毫秒数自动调整内存大小。
-XX:MaxGCPauseMillis=n
-XX:GCTimeRatio=n
-XX:+UseAdaptiveSizePolicy

image.gif

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

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

image.gif

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环境下获得更好的性能表现。

-XX:+UseParallelGC
-XX:+UseParallelOldGC

image.gif


总结

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

相关文章
|
20天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
26 0
|
20天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
23天前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
33 1
|
28天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
226 1
|
2月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
41 4
|
17天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
26天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
27天前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
21 3
|
28天前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
45 1
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。