垃圾收集器ParNewCMS与底层三色标记算法详解

news2024/12/24 8:28:22

垃圾收集算法

在这里插入图片描述

分代收集理论

当前虚拟机的垃圾收集都是采用分代收集算法,这种算法没有什么新思想,只是依据对象的存活周期不同将内存分为几块.一般将Java堆分为新生代和老年代,这样就可以根据各个年代的特点选择合适的垃圾收集算法.

比如在新生代中,每次收集都会有大量对象(近99%)死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集.而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择"标记-清除"或"标记-整理"算法进行垃圾收集.注意:“标记-清除"或"标记-整理算法会比复制算法慢10倍以上”.

标记-复制算法

为了解决效率问题,"复制"收集算法出现了.它可以将内存分为大小相同的两块,每次使用其中的一块.当这一块内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉.这样就每次的内存回收都是对内存区间的一半进行回收.

在这里插入图片描述

标记-清除算法

算法分为"标记"和"清除"阶段;标记存活的对象,统一回收所有未被标记的对象(一般选择这种);也可以反过来,标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象.它是最基础的收集算法,比较简单,但是会带来两个明显的问题:

  1. 效率问题:如果需要标记的对象太多,效率不高
  2. 空间问题:标记清除后会产生大量不连续的碎片

在这里插入图片描述

标记-整理算法

根据老年代的特点特出的一种标记算法,标记过程仍然与"标记-清除"算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存.

在这里插入图片描述

垃圾收集器

在这里插入图片描述

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现.

虽然我们对各个收集器进行比较,但并非为了挑选出一个最好的收集器.因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体的应用场景选择适合自己的垃圾收集器.试想一下:如果有一种四海之内,任何场景下都适用的完美收集器存在,那Java虚拟机就不会实现那么多不同的垃圾收集器了.

Serial收集器

-XX:+UseSerialGC -XX:+UseSerialOldGC

Serial(串行)收集器是最基本,历史最悠久的垃圾收集器了.看名字就知道这个收集器是一个单线程收集器.它的"单线程"的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程(“Stop The World”),直到它收集结束.

新生代采用复制算法,老年代采用标记-整理算法

在这里插入图片描述

虚拟机的设计者当然直到STW带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍在继续).

但是Serial收集器有没有优于其他垃圾收集器的地方呢?

当然有,它 简单而高效(与其他垃圾收集器的单线程相比).Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率.

Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器,主要有以下两种用途:

  • 在JDK1.5及以前的版本中与Parallel Scavenge收集器搭配使用
  • 作为CMS收集器的后备方案

Parallel Scavenge收集器

年轻代:-XX:UseParallelGC

老年代:-XX:UseParallelOldGC

Parallel 收集器其实就是 Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数,收集算法,回收策略等等)和Serial收集器类似.默认的收集线程与CPU核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但一般不推荐修改.

Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU).CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验).所谓吞吐量就是CPU中用于运行用户代码的时间和CPU总消耗时间的比值.Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对收集器运作不太了解的话,可以选择把内存管理优化交给虚拟机负责.

新生代采用复制算法,老年代采用标记-整理算法.

在这里插入图片描述

Parallel Old收集器是Parallel Scavenge收集器的老年代版本.使用多线程和"标记-整理"算法.在注重吞吐量以及CPU资源的场合,都可以优先考虑Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器).

ParNew收集器

-XX:+UseParNewGC

ParNew收集器其实跟 Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用.

新生代采用复制算法

在这里插入图片描述

它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器配合工作

CMS收集器

-XX:+UseConcMarkSweepGC

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器.它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作.

从名字中 Mark Sweep 这两个词可以看出,CMS收集器是一种 标记-清除 算法实现的,它的运作过程相比于前面集中垃圾收集器来说更加复杂一些.整个过程分为四个步骤:

  • 初始标记:暂停所有的其他线程(STW),并记录下GC Roots 直接能引用的对象,速度很快.
  • 并发标记:并发标记就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行.因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变
  • 重新标记:重新标记阶段就是为了修正并发标记期间因为用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(主要是处理漏标问题),这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短.主要用到三色标记里的增量更新算法做重新标记
  • 并发重置:重置本次GC过程中的标记数据

在这里插入图片描述

从它的名字就可以看出它是一款优秀的垃圾收集器,主要有点:**并发收集,低停顿.**但是它有下面几个明显的缺点:

  • 对CPU资源敏感(会和应用程序抢资源)
  • 无法处理 浮动垃圾 (在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次GC再清理了)
  • 使用的回收算法"标记-清除"算法会导致收集结束时会有 大量的空间碎片产生,当然通过参数-XX:+UseCMSCompactAtFullCollection让JVM在执行完标记清除后在做整理
  • 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许还没回收完就再次触发Full GC,也就是"concurrent mode failure",此时会进入STW,用Serial Old垃圾收集器来回收

CMS相关核心参数

  1. -XX:+UseConcMarkSweepGC:启用CMS
  2. -XX:ConcGCThreads:并发的GC线程数
  3. -XX:UseCMSCompactAtFullConllection:FullGC之后做压缩整理(减少碎片)
  4. -XX:CMSFullGCBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC都会压缩一次
  5. -XX:CMSInitiatingOccupancyFraction:当老年代使用达到该比例会触发Full GC(默认92.当老年代使用空间达到92%以后触发Full GC)
  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

垃圾收集算法底层实现

三色标记

在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生.漏标的问题主要引入了三色标记算法来解决.
三色标记算法是把GC Roots可达性分析遍历对象过程中遇到的对象,按照"是否访问过"这个条件标记成以下三种颜色:

  • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过,黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍.黑色对象不可能直接(不经过灰色对象)指向某个白色对象.
  • 灰色:表示对象已经被垃圾收集器访问过,但这个对象至少存在一个引用还没有被扫描过.
  • 白色:表示对象尚未被垃圾收集器访问过.显示在可达性分析刚刚开始的阶段,所有对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达
public class ThreeColorRemarkDemo {

    public static void main(String[] args) {
        A a = new A();
        // 开始做并发标记
        D d = a.b.d; // 读
        a.b.d = null; // 写
        a.d = d; // 写
    }
}

class A{
    B b = new B();
    D d = null;
}

class B{
    C c = new C();
    D d = new D();
}

class C{}

class D{}

在这里插入图片描述

漏标问题复现:

假设A a = new A();后开始做并发标记,从a指向A.从A执行B.从B指向C,此时将A和C记为黑色.B由于还没有扫描到D记为灰色.
在这里插入图片描述
a.b.d = null;将B和D之间的引用给干掉了.
在这里插入图片描述
在并发标记的过程中,应用线程是可以正常执行的.代码此时将a.d = d;但是由于A是黑色.在后面重新标记的过程中是不会扫描黑色的就会出现漏标的问题.

多标-浮动垃圾

在并发标记过程中,如果由于方法运行结束导致部分局部变量(GC Roots)被销毁,这个GC Roots引用的对象之前又被扫描过(被标记为非垃圾对象).那么本轮GC不会回收这部分内存,这部分本该回收但是没有回收的内存,被称之为"浮动垃圾",浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮回收中才被清除.
另外,针对并发标记(还有并发清理)开始后产生的新对象,通常做法是直接全部当成黑色,本轮不会进行清除.这部分对象期间可能也会变成垃圾.这也算是浮动垃圾的一部分.

漏标-读写屏障

漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有以下两种解决方案:

  • 增量更新(Incremental Update):当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次.这可以简化理解为:黑色对象一旦插入了指向白色对象的引用之后,它就变回灰色对象了.
  • 原始快照(Snapshot At The Beginning,SATB):当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,再并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,再重新扫描一次.这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象再本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)

以上无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过 写屏障 实现的.

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

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

相关文章

【Linux开发 第九篇】磁盘分区

Linux磁盘分区 磁盘分区 Linux分区是用来组成整个文件系统的一部分 Linux采用了一种叫载入的处理方法,它的整个文件系统中包括了一整套的文件和目录,且将一个分区和一个目录联系起来,这时要载入的一个分区将使它的存储空间开一个目录下获得…

基于spark进行数据分析的心力衰竭可视化大屏项目

基于spark进行数据分析的心力衰竭可视化大屏项目 项目背景 在当今的医疗领域,数据驱动的决策变得日益重要。心力衰竭作为常见的心血管疾病,其临床数据的分析对于改善患者治疗结果至关重要。本文将介绍如何利用Apache Spark进行大规模心力衰竭临床数据的…

单例模式与反射创建对象

单例模式 饿汉式单例模式 单例模式,就是自己先把自己创建了,整个程序都只有这一个实例,别人都没有办法创建实例,因为他的构造方法是private的 一次性把全部都创建了 public class HungryMan {private static int [][] s new …

linux的编译器vim

vim简介 之前我们在win下写代码,都是下载一些编译器VS/eclipse等 他们不仅可以写代码,还可以实现代码的运行调试,开发。这样的编译器叫做集成编译器 而linux中虽然也有这样的编译器,但不管是从下载,还是使用中都会显…

利用Opencv4.9为图像添加边框

返回:OpenCV系列文章目录(持续更新中......) 上一篇利用OpenCV4.9制作自己的线性滤波器! 下一篇 :OpenCV系列文章目录(持续更新中......) 目标 在本教程中,您将学习如何: 使用 OpenCV 函数 …

Java基于微信小程序的讲座预约系统的研究与实现,附源码

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

拿捏 顺序表(1)

目录 1. 顺序表的分类2. 顺序表实现3. 顺序表实现完整代码4. 总结 前言: 一天xxx想存储一组数据, 并且能够轻松的实现删除和增加, 此时数组大胆站出, 但是每次都需要遍历一遍数组, 来确定已经存储的元素个数, 太麻烦了, 于是迎来了顺序表不屑的调侃: 数组你不行啊… 顺序表是一…

学习亚马逊云科技AWS云计算技术的三款官方免费3A游戏大作

玩3A大作免费电脑游戏,就能成为AWS云架构师、云开发大🐮?这么好的事尊的假的?小李哥今天就来给大家介绍,如何通过玩AWS官方的定制版虚拟人生、炉石传说和密室逃脱游戏学习AWS。这三个游戏完全免费,没有任何…

react之组件与JSX

第一章 - 描述用户界面 概述:React是一个用于构建用户界面(UI)的JavaScript库,用户界面由按钮,文本和图像等小单元内容构建而成。React帮助你把它们组合成可重用,可嵌套的组件。从web端网站到移动端应用&a…

利用Django中的缓存系统提升Web应用性能

👽发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在构建现代Web应用时,性能通常是至关重要的考虑因素之一。为了提高用户体验和应…

云原生Kubernetes: K8S 1.29版本 部署ingress-nginx

目录 一、实验 1.环境 2. K8S 1.29版本 部署ingress-nginx 二、问题 1.kubectl 如何强制删除 Pod、Namespace 资源 2.创建pod失败 3.pod报错ImagePullBackOff 4.docker如何将镜像上传到官方仓库 5.创建ingress报错 一、实验 1.环境 (1)主机 表…

linux 下的 sqlite数据库

SQLite 认识 SQLite简介 轻量化,易用的嵌入式数据库,用于设备端的数据管理,可以理解成单点的数据库。传统服务器型数据库用于管理多端设备,更加复杂 SQLite是一个无服务器的数据库,是自包含的。这也称为嵌入式数据库&…

【免费源码下载】完美运营版商城 虚拟商品全功能商城 全能商城小程序 智慧商城系统 全品类百货商城php+uniapp

简介 完美运营版商城/拼团/团购/秒杀/积分/砍价/实物商品/虚拟商品等全功能商城 干干净净 没有一丝多余收据 还没过手其他站 还没乱七八走的广告和后门 后台可以自由拖曳修改前端UI页面 还支持虚拟商品自动发货等功能 挺不错的一套源码 前端UNIAPP 后端PHP 一键部署版本&am…

随机游走的艺术-图嵌入表示学习

图嵌入引入 机器学习算法: 厨师 样本集: 食材 只有好的食材才能做出好的饭菜 我们需要把数据变成计算机能够读懂的形式(将数据映射成为向量) 图嵌入概述 传统图机器学习 图表示学习 自动学习特征,将…

【Linux驱动层】iTOP-RK3568学习之路(三):字符设备驱动框架

一、总体框架图 二、字符设备相关函数 静态申请设备号 register_chrdev_region 函数原型:register_chrdev_region(dev_t from, unsigned count, const char *name) 函数作用:静态申请设备号,可以一次性申请多个连续的号,count指定…

从头开始构建自己的 GPT 大型语言模型

图片来源: Tatev Aslanyan 一、说明 我们将使用 PyTorch 从头开始构建生成式 AI、大型语言模型——包括嵌入、位置编码、多头自注意、残差连接、层归一化,Baby GPT 是一个探索性项目,旨在逐步构建类似 GPT 的语言模型。在这个项目中&#xff…

【教程】MySQL数据库学习笔记(五)——约束(持续更新)

写在前面: 如果文章对你有帮助,记得点赞关注加收藏一波,利于以后需要的时候复习,多谢支持! 【MySQL数据库学习】系列文章 第一章 《认识与环境搭建》 第二章 《数据类型》 第三章 《数据定义语言DDL》 第四章 《数据操…

R: 阿尔法α多样性计算和箱图制作,以及差异分析

# install.packages("vegan") library(vegan) library(ggplot2) library(ggpubr)setwd("xxx") # 使用read.table()函数读取数据 df <- read.table("xxx", header TRUE, row.names 1)# 转置数据框 df <- t(df)# 计算每个样品的香农多样性…

【论文笔记】基于预训练模型的持续学习(Continual Learning)(增量学习,Incremental Learning)

论文链接&#xff1a;Continual Learning with Pre-Trained Models: A Survey 代码链接&#xff1a;Github: LAMDA-PILOT 持续学习&#xff08;Continual Learning, CL&#xff09;旨在使模型在学习新知识的同时能够保留原来的知识信息了&#xff0c;然而现实任务中&#xff…

《Linux运维总结:Kylin V10+ARM架构CPU基于docker-compose一键离线部署mongodb4.0.11之副本集群》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面向不通的客户安装我们的业务系统&…