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

系列文章目录

第一章 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技术的不断进步,未来我们或许能看到更加智能和高效的垃圾回收机制。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VUE_TypeError: Cannot convert a BigInt value to a number at Math.pow 解决方法

错误信息 TypeError: Cannot convert a BigInt value to a number at Math.pow vue 或 react package.json添加 "browserslist": {"production": ["chrome > 67","edge > 79","firefox > 68","opera >…

Go语言---TCP服务端以及客服端的实现

TCP的C/S架构 TCP服务器的实现 监听的底层实现 func Listen(network, address string) (Listener, error) {var lc ListenConfigreturn lc.Listen(context.Background(), network, address) }type Listener interface {// Accept waits for and returns the next connection …

每日Attention学习10——Scale-Aware Modulation

模块出处 [ICCV 23] [link] [code] Scale-Aware Modulation Meet Transformer 模块名称 Scale-Aware Modulation (SAM) 模块作用 改进的自注意力 模块结构 模块代码 import torch import torch.nn as nn import torch.nn.functional as Fclass SAM(nn.Module):def __init__…

C++笔试强训5

文章目录 一、选择题1-5题6-10题 二、编程题题目一题目二 一、选择题 1-5题 x1,先x,再x–,while判断永远为真,故死循环 选D。 sizeof会计算\0,strlen不包括\0,并且strlen只计算\0之前的。 所以sizeof是10,strken是4 …

纯净IP的重要性解析与测评分析

作为连接互联网世界的桥梁,IP地址的纯净度不仅关乎网络访问的速度与稳定性,更是影响着数据安全与隐私保护。今天,我们将带您深入探索纯净IP的重要性,并分享我们对芝麻HTTP与巨量IP这两家提供纯净SOCKS5代理服务的深度测评分析。 一…

SSM整合--笔记总结

1.概述 ssm(springmvc spring mybatis)这三个框架的整合。 spring和springmvc他们隶属于一家公司,他们无需整合。 spring和mybatis框架的整合。 spring把mybatis中的配置内容放到自己的配置文件中。因为我们可以让tomcat加载spring配置文件。 思考:mybatis配置文件…

240710_昇思学习打卡-Day22-条件随机场

240710_昇思学习打卡-Day22-条件随机场 在正式开始LSTMCRF序列标注之前,我们先来了解一下条件随机场,以下仅做简单介绍。 CRF全称Conditional Random Field,按照名字来理解,条件随机,随机输入,条件输出。…

老物件线上3D回忆展拓宽了艺术作品的展示空间和时间-深圳华锐视点

在数字技术的浪潮下,3D线上画展为艺术家们开启了一个全新的展示与销售平台。这一创新形式不仅拓宽了艺术作品的展示空间,还为广大观众带来了前所未有的观赏体验。 3D线上画展制作以其独特的互动性,让艺术不再是单一的视觉享受。在这里&#x…

计算机网络之网络互连

1.什么是网络互连 1.1网络互连的目的 将两个或者两个以上具有独立自治能力的计算机网络连接起来,实现数据流通,扩大资源共享范围,或者容纳更多用户。 网络互连包括: 同构网络、异构网络的互连, 局域网与局域网&…

JMX脚本组成分析

JMX脚本组成分析 前言 在前两集,我们已经完成了项目与环境管理开发的实战。 通过观察E-R图,我们下一步的内容就是要去完成压测模块的内容,那么在完成压测模块的内容之前,我们要计划一下如何压测,以及要从哪个角度切…

今天我们来聊Java IO模型,BIO、NIO、AIO三种常见IO模型

一、写在开头 很久没更新喽,最近build哥一直在忙着工作,忙着写小说,都忘记学习自己的本职了,哈哈,不过现在正式回归! 我们继续学习Java的IO相关内容,之前我们了解到,所谓的IO&#…

Study--Oracle-07-ASM自动存储管理(一)

一、ASM实例和数据库实例对应关系 1、ASM是Oracle 10g R2中为了简化Oracle数据库的管理而推出来的一项新功能,这是Oracle自己提供的卷管理器,主要用于替代操作系统所提供的LVM,它不仅支持单实例,同时对RAC的支持也是非常好。ASM可…

thinkphp 生成邀请推广二维码,保存到服务器并接口返回给前端

根据每个人生成自己的二维码图片,接口返回二维码图片地址 生成在服务器的二维码图片 控制器 public function createUserQRcode(){$uid = input(uid);if

Redis实践经验

优雅的Key结构 Key实践约定: 遵循基本格式:[业务名称]:[数据名]:id例:login:user:10长度步超过44字节(版本不同,上限不同)不包含特殊字符 优点: 可读性强避免key冲突方便管理节省内存&#x…

Java02--基础概念

一、注释 注释是在程序指定位置添加的说明性信息 简单理解,就是对代码的一种解释 1.单行注释 格式: //注释信息 2.多行注释 格式: /*注释信息*/ 3.文档注释 格式: /**注释信息*/ 注释使用的细节: 注释内容不会参与编译和运…

向量数据库:faiss的常用三种数据索引方式(IndexFlatL2,IndexIVFFlat,IndexIVFPQ)的使用和持久化+索引融合的实现及库函数解读

常用的三种索引方式 Faiss 中有常用的三种索引方式:IndexFlatL2、IndexIVFFlat 和 IndexIVFPQ。 1.IndexFlatL2 - 暴力检索L2: 使用欧氏距离(L2)进行精确检索。适用于较小规模的数据集,采用暴力检索的方式&#xff0…

【可视化大屏系列】Echarts之折线图绘制

本文为个人近期学习总结,若有错误之处,欢迎指出! Echarts之折线图绘制 前言1.需求2.实现效果3.大概思路4.代码实现子组件写法父组件写法 前言 在前文页面布局、DataV 的使用、Echarts 的基础使用的基础上,开始绘制大屏中的折线图…

C++——二叉搜索树的实现

1、二叉搜索树的概念 二叉搜索树又叫做二叉排序树,他或者是一棵空树,或者具有以下性质: 若他的左子树不为空,则左子树的所有节点的值都小于根节点的值, 若他的右子树不为空,则右子树的所有节点的值都大于…

论文翻译:Rethinking Interpretability in the Era of Large Language Models

https://arxiv.org/abs/2402.01761 在大型语言模型时代的可解释性再思考 摘要 在过去十年中,随着越来越大的数据集和深度神经网络的兴起,可解释机器学习领域的兴趣迅速增长。同时,大型语言模型(LLMs)在广泛的任务中…

JavaScript 中 await 永远不会 resolve 的 Promise 会导致内存泄露吗?

前言 在 JavaScript 中,await 关键字用于等待一个 Promise 完成,它只能在异步函数(async function)内部使用。当 await 一个永远不会 resolve 的 Promise 时,它确实会阻塞异步函数的进一步执行,但不会直接…