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

垃圾收集算法

在这里插入图片描述

分代收集理论

当前虚拟机的垃圾收集都是采用分代收集算法,这种算法没有什么新思想,只是依据对象的存活周期不同将内存分为几块.一般将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.hqwc.cn/news/637575.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MKS GM50A MFC GUI 软件使用指南GE50A调零原理及步骤PPT

MKS GM50A MFC GUI 软件使用指南GE50A调零原理及步骤PPT

介绍与部署 Zabbix 监控系统

目录 前言 一、监控系统 1、主流的监控系统 2、监控系统功能 二、Zabbix 监控系统概述 1、Zabbix 概念 2、Zabbix 主要特点 3、Zabbix 主要功能 4、Zabbix 监控对象 5、Zabbix 主要程序 6、Zabbix 监控模式 7、Zabbix 运行机制 8、Zabbix 监控原理 9、Zabbix 主…

Mac 下安装PostgreSQL经验

使用homebrew终端软件管理器去安装PostgreSQL 如果没有安装brew命令执行以下命令 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 沙果开源物联网系统 SagooIoT | SagooIoT 1.使用命令安装postgreSQL brew i…

java的单元测试和反射

单元测试 就是针对最小的功能单元,编写测试代码对其进行正确性测试 Junit单元测试框架: 可以用来对方法进行测试 有点: 可以灵活的编写测试代码,可以针对某个方法进行测试,也支持一键完成对全部方法的自动发测试&a…

开源王者!全球最强的开源大模型Llama3发布!15万亿数据集训练,最高4000亿参数,数学评测超过GPT-4,全球第二!

本文原文来自DataLearnerAI官方网站: 开源王者!全球最强的开源大模型Llama3发布!15万亿数据集训练,最高4000亿参数,数学评测超过GPT-4,全球第二! | 数据学习者官方网站(Datalearner)https://ww…

使用Python比较两张人脸图像并获得准确度

使用 Python、OpenCV 和人脸识别模块比较两张图像并获得这些图像之间的准确度水平。 一、原理 使用Face Recognition python 模块来获取两张图像的128 个面部编码,并比较这些编码。比较结果返回 True 或 False。如果结果为True ,那么两个图像将是相同的…

2024团体程序设计天梯赛L1-104 九宫格

题目链接L1-104 九宫格 #include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; int n, mapp[10][10], a[10]; int dx[10]{0, 1, 1, 1, 4, 4, 4, 7, 7, 7}; int dy[10]{0, 1, 4, 7, 1, 4, 7, 1, 4, 7}; b…

2、关于数据库事务那些事

目录 1、什么是事务&#xff1f; 2、介绍下数据库事务&#xff1f; 3、并发事务会带来什么问题&#xff1f; 3.1、不可重复读和幻读有什么区别&#xff1f; 4、数据库隔离级别有哪几种&#xff1f; 5、MySQL默认使用隔离级别是啥&#xff1f; 6、如何控制并发事务&#…

山东大学操作系统实验一(Linux虚拟机实现)

目录 实验题目 实验要求 示例程序 主程序 头文件 重点代码解析 一、main函数的参数 参数介绍 参数输入方式 本块代码 二、信号处理 本块代码 原理介绍 实现效果 三、kill函数 功能介绍 使用方式 本块代码 四、头文件处理 本块代码 代码作用 实验程序 …

2024团体程序设计天梯赛L1-103 整数的持续性

题目链接L1-103 整数的持续性 #include<iostream> #include<stdio.h> #include<algorithm> using namespace std; struct node{int x;int d; }p[2000]; bool cmp(node a, node b) {if (a.d b.d) return a.x < b.x;return a.d>b.d; } int cnt, cntt; v…

日期相关的题目

日期相关的题目 1. 计算日期到天数转换2. 日期累加3. 打印日期4. 日期差值 1. 计算日期到天数转换 输出示例: 思路&#xff1a;计算前n-1个月的天数在加上这个月的天数。 #include <iostream> using namespace std;int main() {int year, month, day;cin >> yea…

自学注解----

MainApplication.java SpringBootApplication类就是—主程序类&#xff0c;告诉你这是一个SpringBoot应用 SpringBootConfiguration—就是Configuration&#xff0c;当前一个配置类 EnableAutoConfiguration— AutoConfigurationPackage自动配置包&#xff0c;包含Import导入包…