Java核心篇之JVM探秘:垃圾回收算法与垃圾收集器

news2024/11/15 8:26:02

系列文章目录

第一章 Java核心篇之JVM探秘:内存模型与管理初探

第二章 Java核心篇之JVM探秘:对象创建与内存分配机制

第三章 Java核心篇之JVM探秘:垃圾回收算法与垃圾收集器

第四章 Java核心篇之JVM调优实战:Arthas工具使用及GC日志分析


目录

前言

一、垃圾收集算法

引用计数法 (Reference Counting)

标记清除算法 (Mark-Sweep)

复制算法 (Copying)

标记整理算法 (Mark-Compact 或 Mark-Sweep-Compact)

分代算法 (Generational)

增量回收算法 (Incremental Garbage Collection)

二、垃圾收集器

(1)Serial Collector(串行收集器)

(2)Parallel Collector(并行收集器)

(3)ParNew Collector(并行新生代收集器)

(3)CMS 收集器

(4)G1 收集器

(5)ZGC (Z Garbage Collector)

三、垃圾收集底层算法

(1)三色标记

示例

(2)颜色指针

总结


前言

        前边我们已经了解过了JVM的内存模型与内存分配机制,在Java的世界里,内存管理是一项至关重要的任务。与C或C++等语言不同,Java自动处理对象的创建和销毁,这一过程主要由Java虚拟机(JVM)中的垃圾回收器(GC)来完成。本文将深入探讨垃圾回收算法与垃圾收集器的细节,旨在为读者提供一个全面且深入的理解。


一、垃圾收集算法

垃圾回收(Garbage Collection, GC)是现代编程语言运行环境中的一个重要特性,它自动管理内存的分配和释放,避免了程序员手动管理内存可能引入的错误。以下是一些常见的垃圾回收算法及其特点:

引用计数法 (Reference Counting)

  • 思想:为每个对象关联一个引用计数器,每当有一个地方引用它,计数器就增加1;当引用失效时,计数器减少1。当计数器为0时,表示没有引用指向该对象,可以被回收。
  • 优点:实现简单,不需要停止整个应用程序,可以立即回收无引用的对象。
  • 缺点:无法处理循环引用的情况,因为即使对象之间形成循环引用链,它们的引用计数也不会降到0。

标记清除算法 (Mark-Sweep)

  • 思想:分为“标记”和“清除”两个阶段。首先从根节点开始标记所有可达对象,然后清除未被标记的对象。
  • 优点:可以有效回收不再使用的对象。
  • 缺点:执行过程中需要暂停应用程序(Stop-the-world),并且会留下内存碎片,可能导致后续分配大对象时失败。

复制算法 (Copying)

  • 思想:将内存区域划分为两块相等的区域,每次只使用其中一个区域,垃圾回收时将存活对象复制到另一个区域,然后清空当前区域。
  • 优点:适用于新生代,其中大部分对象是短暂的,因此复制成本低,同时可以消除内存碎片。
  • 缺点:需要双倍的内存空间,并且如果存活对象过多,可能需要频繁复制,影响性能。

标记整理算法 (Mark-Compact 或 Mark-Sweep-Compact)

  • 思想:结合了标记清除算法和内存整理过程,除了标记和清除之外,还会将存活的对象移动到内存的一端,从而消除内存碎片。
  • 优点:解决了标记清除算法的内存碎片问题。
  • 缺点:移动对象需要更新所有指向这些对象的指针,可能会导致额外的开销。

分代算法 (Generational)

  • 思想:基于观察到的现象,大多数对象很快就会变得不可达(短暂生存期)。将堆分为几代(如新生代和老年代),使用不同的算法处理不同代的对象。
  • 优点:可以优化垃圾回收过程,减少全局停顿时间,提高回收效率。
  • 缺点:算法实现复杂,需要维护不同代之间的转换逻辑。

增量回收算法 (Incremental Garbage Collection)

  • 思想:将垃圾回收工作分成多个小的步骤,逐步完成,而不是一次性完成,这样可以减少停顿时间。
  • 优点:可以显著降低应用程序的暂停时间。
  • 缺点:实现复杂,需要精细控制垃圾回收的进度。

二、垃圾收集器

虽然我们对各个收集器进行比较,但并非为了挑选出一个最好的收集器。更加没有万能的收集器,开发者应根据具体应用场景选择合适的GC算法,并通过调优提高系统效率。随着JVM的不断演进,未来的垃圾收集器将更加智能和高效。

(1)Serial Collector(串行收集器)

描述:这是最简单的垃圾收集器,使用单线程执行垃圾回收操作。在执行GC时,所有其他线程都会暂停。

算法:年轻代使用复制算法,老年代使用标记-整理算法。

优势:消耗资源少,适用于小内存环境。

劣势:GC停顿时间长,不适合多核处理器。

适用场景:小内存、单核处理器的环境,或测试环境。

参数:-XX:+UseSerialGC -XX:+UseSerialOldGC(明确指定使用串行收集器)。

(2)Parallel Collector(并行收集器)

描述:

  • Parallel Collector在年轻代使用多个线程进行垃圾收集,以减少GC停顿时间。

算法:

  • 年轻代使用复制算法,老年代使用标记-压缩算法。

优势:

  • 多线程可以加快GC速度,减少停顿时间。

劣势:

  • 在小堆内存中,多线程的优势不明显。

适用场景:

  • 多核处理器、对GC停顿时间敏感的应用。

参数:

  • -XX:+UseParallelGC(年轻代)、-XX:+UseParallelOldGC(老年代)。

(3)ParNew Collector(并行新生代收集器)

描述:ParNew是Parallel Collector的年轻代版本,它使用多线程进行垃圾收集。

算法:使用复制算法。

优势:多线程可以减少GC停顿时间,适合多核处理器。

劣势:与Serial Collector相比,消耗更多系统资源。

适用场景:多核处理器、需要与CMS Collector配合使用的情况。

参数:-XX:+UseParNewGC(启用ParNew收集器)。

(3)CMS 收集器

描述:CMS Collector旨在最小化停顿时间,它在标记阶段与应用程序线程并发运行。

算法:使用标记-清除算法,但在清除阶段可能会暂停应用程序。

优势:低停顿时间,适合交互式应用。

劣势:可能导致内存碎片,不适合大堆内存。

适用场景:需要低停顿时间、对用户响应敏感的应用。

参数

  1. -XX:+UseConcMarkSweepGC:启用cms
  2. -XX:ConcGCThreads:并发的GC线程数

  3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)

  4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次

  5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)

  6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,
    JVM仅在第一次使用设定值,后续则会自动调整

  7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,降低CMS GC标记阶段(也会对年轻代一起做标记,如果在
    minor gc就干掉了很多对垃圾对象,标记阶段就会减少一些标记时间)时的开销,一般CMS的GC耗时 80%都在标记阶段

  8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW

  9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

(4)G1 收集器

描述:

G1是一种基于分区的垃圾收集器,它可以预测停顿时间,并尝试将停顿时间保持在一个可接受的范围内,即使在大堆内存中也是如此。

优点:均衡停顿时间和内存碎片,适合大堆内存。

缺点:配置复杂度较高,需要更多试验才能找到最佳配置。

使用场景:适合需要低停顿时间和大堆内存的企业级应用。

参数:

  1. -XX:+UseG1GC:使用G1收集器
  2. -XX:ParallelGCThreads:指定GC工作的线程数量
  3. -XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
  4. -XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
  5. -XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)
  6. -XX:G1MaxNewSizePercent:新生代内存最大空间
  7. -XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
  8. -XX:MaxTenuringThreshold:最大年龄阈值(默认15)
  9. -XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能就要触发MixedGC了
  10. -XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这个值,存活对象过多,回收的的意义不大。
  11. -XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。
  12. -XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。

(5)ZGC (Z Garbage Collector)

描述:

ZGC是一款JDK 11中新加入的具有实验性质的低延迟垃圾收集器,ZGC可以说源自于是Azul System公司开发的C4(Concurrent Continuously Compacting Collector) 收集器。

优点:极低的停顿时间,适合需要极低延迟的大数据应用。

缺点:新特性,可能在某些环境中稳定性未知。

使用场景:适合超大堆内存和对延迟敏感的应用。

参数:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC(开启ZGC收集器)。

三、垃圾收集底层算法

(1)三色标记

三色标记算法是垃圾回收器(Garbage Collector, GC)中用于并发标记阶段的一种技术,主要用于解决在并发环境中准确标记存活对象的问题。这种算法由三种不同的“颜色”表示对象的不同状态,分别是白色、灰色和黑色。

在并发标记算法中,三色标记的作用在于帮助垃圾回收器追踪和标记所有可达的对象,同时处理在标记过程中由于其他线程修改对象引用而可能产生的漏标或错标问题。

下面是三色标记的基本概念:

  • 白色(White):表示尚未被访问或标记的对象。在垃圾收集开始时,所有的对象都被认为是白色的。

  • 灰色(Gray):表示已经开始访问但其引用还未完全检查的对象。当一个对象首次被发现并且其引用列表尚未被检查时,它会被标记为灰色。

  • 黑色(Black):表示已经完全访问和标记的对象。一旦一个对象的所有引用都被检查完毕,并且确定它是可达的,那么它就会被标记为黑色。

在并发标记过程中,如果一个新的引用指向了一个白色的对象,那么这个对象会被标记为灰色,以确保它能被正确地加入到待检查的队列中。此外,当一个灰色对象的引用被访问时,如果这个引用指向的是白色对象,那么这个对象也会被标记为灰色,从而保证了所有可达对象都能被正确标记。

为了防止在并发标记阶段出现的数据竞争和不一致性,垃圾收集器会使用诸如读写屏障等技术来确保引用的正确更新和对象状态的同步。

三色标记算法是CMS和G1等现代垃圾收集器中使用的关键技术之一,它有助于实现高效、低中断的垃圾收集过程。

示例

假设现在有白、灰、黑三个集合(表示当前对象的颜色),其遍历访问过程为:

  1. 初始时,所有对象都在【白色集合】中;
  2. 将GC Roots直接引用到的对象挪到【灰色集合】中;
  3. 从灰色集合中获取对象:
    1. 将本对象引用到的其他对象全部挪到【灰色集合】中;
    2. 将本对象挪到【黑色集合】里面。
  4. 重复步骤3,直至【灰色集合】为空时结束。
  5. 结束后,仍在【白色集合】的对象即为GC Roots不可达,可以进行回收。

需要注意,传统标记方式发生Stop The World时,对象间的引用是不会发生变化的,可以轻松完成标记。

而并发标记在标记期间应用线程还在继续跑,对象间的引用可能发生变化,就会出现错标和漏标的情况就有可能发生。

(2)颜色指针

颜色指针(Colored Pointers)是一种用于辅助并发标记阶段的技术,它的设计目的是为了在不中断应用程序执行的情况下,高效地进行垃圾收集,同时避免数据竞争和标记错误。

在传统的并发标记算法中,如果多个线程同时访问同一对象,或者在标记过程中对象的引用发生变化,就可能产生标记不一致的情况,即所谓的“漏标”和“错标”。为了避免这些问题,ZGC引入了颜色指针的概念。

颜色指针包含两部分信息:

  1. 对象地址:存储实际对象的内存位置。
  2. 颜色标记:一个额外的位(或几位),用来标识该指针指向的对象是否已经被标记。

在ZGC中,颜色标记有两种状态:

  • 黑色(Black):表示该对象已经被标记为存活。
  • 白色(White):表示该对象还没有被标记,可能是未访问或未存活的对象。

ZGC如何利用颜色指针:

  1. 初始化:在垃圾收集开始时,所有指针的颜色标记都会被设为白色。
  2. 标记过程:当一个对象被访问时,它的颜色标记会变成黑色。同时,任何指向该对象的指针的颜色也会被更新为黑色,表明这个对象是存活的。
  3. 读写屏障:在并发标记过程中,每当一个线程读取或修改对象引用时,ZGC会使用读写屏障来更新指针的颜色。如果一个线程读取了一个白色指针,它会将该指针标记为黑色,并将这个对象加入到待检查的队列中。如果一个线程尝试修改一个黑色指针,它会先检查目标对象的颜色,如果目标对象仍然是白色,则将其标记为黑色,并同样将该对象加入到待检查的队列中。

颜色指针技术允许ZGC在并发标记阶段安全地处理对象引用的变化,确保所有存活对象能够被正确标记,同时最小化了对应用程序执行的干扰。这种机制使得ZGC能够在高并发环境下提供非常低的暂停时间和高效的内存管理。

需要注意的是,颜色指针的实现细节可能会因具体实现和处理器架构而异,例如在64位系统中,颜色位可能直接编码在指针值中,而在某些情况下,可能需要使用额外的元数据来存储颜色信息。

如上图所示,64位对象引用划分如下:
18位:未使用的位
1位:可最终确定
1位:重新映射
1位:标记1
1位:标记为0
42位:对象地址
前18位保留供将来使用。42位可以寻址高达4 TB的数据。。。 


总结

        理解垃圾回收算法与垃圾收集器不仅有助于开发者编写更高效的代码,还能在遇到性能瓶颈时,提供有效的诊断和优化策略。随着JVM技术的不断进步,未来我们或许能看到更加智能和高效的垃圾回收机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1924792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT creator与VS2019 QT加载模块方法

QT creator与VS2019加载模块方法 QT creator,pro文件添加 VS2019 QT

JavaScript中的面向对象编程

OPP在JavaScript的表现方式:原型 传统的OPP:类 ● 对象(实例)由类实例化,类的功能类似于蓝图,通过蓝图来实现建筑(实例) ● 行为(方法)从类复制到所有实例 …

子进程继承父进程文件描述符导致父进程打开设备文件失败

开发过程中有时会遇到需要在程序中执行三方程序或者shell脚本,一般会通过system(), popen(), exec簇来完成该功能。我们知道以上方法会通过fork创建子进程后在子进程中执行相应指令。如图1为某个示例流程,具体的程序执行流程如图2所示,线程my…

Android ListView

ListView ListView是以列表的形式展示具体内容的控件,ListView能够根据数据的长度自适应显示,如手机通讯录、短消息列表等都可以使用ListView实现。如图1所示是两个ListView,上半部分是数组形式的ListView,下半部分是简单列表Lis…

WPF学习(5) -- WPF绑定

一、双向绑定 1.代码示例 <Window x:Class"学习.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expres…

批量导出word,并导出一个zip文件

系统导出功能&#xff0c;多条数据分别导出word&#xff0c;多个word打包到一个zip进行导出&#xff0c;直接拷贝过去可用&#xff0c;如果缺包自行查找。 参考&#xff1a; Java使用word模板导出word_java根据模板导出word-CSDN博客. Action(value "exportToWordZip&qu…

高阶数据结构——并查集

1. 并查集的介绍 并查集是一种树型的数据结构&#xff0c;用于处理一些不相交集合的合并及查询问题&#xff08;即所谓的并、查&#xff09;。 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#…

springboot+vue 开发记录(九)后端打包部署运行

本篇文章主要内容是后端项目写好了&#xff0c;怎么打包部署到服务器上运行。 文章目录 1. 在服务器上安装Docker2. 在Docker中装MySQL3. 在Docker中设置网桥&#xff0c;实现容器间的网络通信4. 修改后端配置文件5. 修改pom.xml文件6. 打包7. 编写DockerFile文件8. 上传文件到…

STFT:解决音频-视频零样本学习 (ZSL) 中的挑战

传统的监督学习方法需要大量的标记训练实例来进行训练,视听零样本学习的任务是利用音频和视频模态对对象或场景进行分类&#xff0c;即使在没有可用标记数据的情况下。为了解决传统监督方法的限制&#xff0c;提出了广义零样本学习&#xff08;Generalized Zero-Shot Learning,…

暴雨让服务器不怕热҈热҈热҈热҈

在AI算力呈几何倍数增长的趋势下&#xff0c;算力逐渐朝着“高性能、高密度、高耗能“发展。在高耗能的算力下&#xff0c;AI服务器功率已逐步逼近风冷散热极限&#xff0c;而液冷作为更加高效、低能耗的制冷技术&#xff0c;逐渐成为了高密度算力散热场景的首选方案。 液冷的…

Spring源码中的模板方法模式

1. 什么是模板方法模式 模板方法模式&#xff08;Template Method Pattern&#xff09;是一种行为设计模式&#xff0c;它在操作中定义算法的框架&#xff0c;将一些步骤推迟到子类中。模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤。 模板方法模式的定义&…

Linux内核编译安装 - Deepin,Debian系

为什么要自己编译内核 优点 定制化&#xff1a;你可以根据自己的硬件和需求配置内核&#xff0c;去掉不必要的模块&#xff0c;优化性能。性能优化&#xff1a;移除不需要的驱动程序和特性&#xff0c;减小内核体积&#xff0c;提高系统性能。最新特性和修复&#xff1a;获取…

网络(二)——套接字编程

文章目录 理解源IP地址和目的IP地址认识端口号认识TCP/UDP协议网络字节序socket编程接口socket 常见APIsockaddr结构 理解源IP地址和目的IP地址 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址&#xff1b; 源IP即发送方的地址&#xff0c;目的IP即接受方的…

[译] Rust标准库有些特殊,让我们改它

本篇是对 RustConf 2023中的The standard library is special. Let’s change that.这一视频的翻译与整理, 过程中为符合中文惯用表达有适当删改, 版权归原作者所有. 今天我将讨论Rust的标准库,更具体地说,是关于标准库有何特殊之处,以及为什么我们应该改变这一点。首先声明一下…

探索 Prompt 的世界:让你的 AI 更智能

探索 Prompt 的世界&#xff1a;让你的 AI 更智能 引言什么是 Prompt&#xff1f;Prompt 的重要性如何编写有效的 Prompt1. 清晰明确2. 包含关键细节3. 提供上下文 实践中的 Prompt 技巧1. 多次迭代2. 实验不同风格3. 结合实际应用 总结 引言 随着人工智能&#xff08;AI&…

通过vm可以访问那些属性——06

1.通过vue实例都可以访问那些属性&#xff1f;&#xff08;通过vm都可以vm.什么&#xff09; vue实例中的属性很多。有的以$开始&#xff0c;有的以_开始。 所有以$开始的属性&#xff0c;可以看做是公开的属性&#xff0c;这些属性是提供给程序员使用的 所有以_开始的属性&…

PyTorch是使用GPU和CPU优化的深度学习张量库——torchvision

torchvision datasets torchvision.datasets 包含了许多标准数据集的加载器。例如&#xff0c;CIFAR10 和 ImageFolder 是其中两个非常常用的类。 CIFAR10 CIFAR10 数据集是一个广泛使用的数据集&#xff0c;包含10类彩色图像&#xff0c;每类有6000张图像&#xff08;5000张…

<数据集>夜间车辆识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;5000张 标注数量(xml文件个数)&#xff1a;5000 标注数量(txt文件个数)&#xff1a;5000 标注类别数&#xff1a;8 标注类别名称&#xff1a;[car, pedestrian, traffic light, traffic sign, bicycle, bus, truck…

Leetcode(经典题)day2

H指数 274. H 指数 - 力扣&#xff08;LeetCode&#xff09; 先对数组排序&#xff0c;然后从大的一头开始遍历&#xff0c;只要数组当前的数比现在的h指数大就给h指数1&#xff0c;直到数组当前的数比现在的h指数小的时候结束&#xff0c;这时h的值就是要返回的结果。 排序…

Ubuntu搭建Android架构so库交叉编译环境

目录 前言一、下载NDK并安装二、安装NDK三、配置交叉编译工具链四、编写交叉编译脚本 前言 需要将一些源码编译成Android可用的架构的so库 一、下载NDK并安装 https://developer.android.google.cn/ndk/downloads/ 二、安装NDK 将下载下来的android-ndk-r23b-linux.zip解压…