09-JVM垃圾收集底层算法实现

上一篇:08-JVM垃圾收集器详解

1.三色标记

在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。

这里我们引入“三色标记”来给大家解释下,把Gcroots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以下三种颜色:

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

在这里插入图片描述

/*** 垃圾收集算法细节之三色标记* 为了简化例子,代码写法可能不规范,请忽略* Created by 诸葛老师*/
public class ThreeColorRemark {public static void main(String[] args) {A a = new A();//开始做并发标记D d = a.b.d;   // 1.读a.b.d = null;  // 2.写a.d = d;       // 3.写}
}class A {B b = new B();D d = null;
}class B {C c = new C();D d = new D();
}class C {
}class D {
}

2.多标-浮动垃圾

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

3.漏标-读写屏障

漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB) 。
增量更新就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后, 它就变回灰色对象了。
原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)
以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过写屏障实现的。

1.写屏障

给某个对象的成员变量赋值时,其底层代码大概长这样:

/**
* @param field 某对象的成员变量,如 a.b.d 
* @param new_value 新值,如 null
*/
void oop_field_store(oop* field, oop new_value) { *field = new_value; // 赋值操作
} 

所谓的写屏障,其实就是指在赋值操作前后,加入一些处理(可以参考AOP的概念):
void oop_field_store(oop* field, oop new_value) {  pre_write_barrier(field);          // 写屏障-写前操作*field = new_value; post_write_barrier(field, value);  // 写屏障-写后操作
}

写屏障实现SATB
当对象B的成员变量的引用发生变化时,比如引用消失(a.b.d = null),我们可以利用写屏障,将B原来成员变量的引用对象D记录下来:

void pre_write_barrier(oop* field) {oop old_value = *field;    // 获取旧值remark_set.add(old_value); // 记录原来的引用对象
}

写屏障实现增量更新
当对象A的成员变量的引用发生变化时,比如新增引用(a.d = d),我们可以利用写屏障,将A新的成员变量引用对象D记录下来:

void post_write_barrier(oop* field, oop new_value) {  remark_set.add(new_value);  // 记录新引用的对象
}

2.读屏障

oop oop_field_load(oop* field) {pre_load_barrier(field); // 读屏障-读取前操作return *field;
}

读屏障是直接针对第一步:D d = a.b.d,当读取成员变量时,一律记录下来:

void pre_load_barrier(oop* field) {  oop old_value = *field;remark_set.add(old_value); // 记录读取到的对象
}

现代追踪式(可达性分析)的垃圾回收器几乎都借鉴了三色标记的算法思想,尽管实现的方式不尽相同:比如白色/黑色集合一般都不会出现(但是有其他体现颜色的地方)、灰色集合可以通过栈/队列/缓存日志等方式进行实现、遍历方式可以是广度/深度遍历等等。

对于读写屏障,以Java HotSpot VM为例,其并发标记时对漏标的处理方案如下:

  • CMS:写屏障 + 增量更新
  • G1,Shenandoah:写屏障 + SATB
  • ZGC:读屏障

工程实现中,读写屏障还有其他功能,比如写屏障可以用于记录跨代/区引用的变化,读屏障可以用于支持移动对象的并发执行等。功能之外,还有性能的考虑,所以对于选择哪种,每款垃圾回收器都有自己的想法。

为什么G1用SATB?CMS用增量更新?

我的理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描。

4.记忆集与卡表

在新生代做GCRoots可达性扫描过程中可能会碰到跨代引用的对象,这种如果又去对老年代再去扫描效率太低了。
为此,在新生代可以引入记录集(Remember Set)的数据结构(记录从非收集区到收集区的指针集合),避免把整个老年代加入GCRoots扫描范围。事实上并不只是新生代、 老年代之间才有跨代引用的问题, 所有涉及部分区域收集(Partial GC) 行为的垃圾收集器, 典型的如G1、 ZGC和Shenandoah收集器, 都会面临相同的问题。
垃圾收集场景中,收集器只需通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针即可,无需了解跨代引用指针的全部细节。
hotspot使用一种叫做“卡表”(Cardtable)的方式实现记忆集,也是目前最常用的一种方式。关于卡表与记忆集的关系, 可以类比为Java语言中HashMap与Map的关系。
卡表是使用一个字节数组实现:CARD_TABLE[ ],每个元素对应着其标识的内存区域一块特定大小的内存块,称为“卡页”。
hotSpot使用的卡页是2^9大小,即512字节
在这里插入图片描述

一个卡页中可包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成1,表示该元素变脏,否则为0.
GC时,只要筛选本收集区的卡表中变脏的元素加入GCRoots里。

卡表的维护
卡表变脏上面已经说了,但是需要知道如何让卡表变脏,即发生引用字段赋值时,如何更新卡表对应的标识为1。
Hotspot使用写屏障维护卡表状态。

下一篇:

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

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

相关文章

golang 通用的 grpc http 基础开发框架

go-moda golang 通用的 grpc http 基础开发框架仓库地址: https://github.com/webws/go-moda仓库一直在更新,欢迎大家吐槽和指点 特性 transport: 集成 http(echo、gin)和 grpc。tracing: openTelemetry 实现微务链路追踪pprof: 分析性能config: 通用…

【云计算网络安全】解析DDoS攻击:工作原理、识别和防御策略 | 文末送书

文章目录 一、前言二、什么是 DDoS 攻击?三、DDoS 攻击的工作原理四、如何识别 DDoS 攻击五、常见的 DDoS 攻击有哪几类?5.1 应用程序层攻击5.1.1 攻击目标5.1.2 应用程序层攻击示例5.1.3 HTTP 洪水 5.2 协议攻击5.2.1 攻击目标5.2.2 协议攻击示例5.2.3 …

IDEA集成Apipost Helper实现一键部署接口(避免参数注释)

先说好处: 1.一次性导入所有接口,不要一个一个扒。 2.对于字段的注释不要一个一个的去手写,映射实体类,自己上传(最重要)。 3.目录自动归类划分,避免接口混乱。 安装插件 首先,我们打…

Apache nginx解析漏洞复现

文章目录 空字节漏洞安装环境漏洞复现 背锅解析漏洞安装环境漏洞复现 空字节漏洞 安装环境 将nginx解压后放到c盘根目录下: 运行startup.bat启动环境: 在HTML文件夹下有它的主页文件: 漏洞复现 nginx在遇到后缀名有php的文件时,…

基于springboot实现了后台定时统计数据报表并将数据生成excel文件作为附件,然后通过邮件发送通知的功能

概述 本例子基于springboot实现了后台定时统计数据报表并将数据生成excel文件作为附件,然后通过邮件发送通知的功能。 详细 一、准备工作 1、首先注册两个邮箱,一个发送邮箱,一个接收邮箱。 2、发送邮箱开启IMAP/SMTP/POP3服务&#xff0c…

【嵌入式开发 Linux 常用命令系列 7.1 -- awk 过滤列中含有特定字符的行】

文章目录 awk 过滤列中字符串 上篇文章:嵌入式开发 Linux 常用命令系列 7 – awk 常用方法详细介绍 awk 过滤列中字符串 cat test.log | awk -F $31 {print $0}说明: -F 以什么分隔列,这里是以空格为分隔符;$3代表第3列;$3…

2023 年全国大学生数学建模B题目-多波束测线问题

B题目感觉属于平面几何和立体几何的问题,本质上需要推导几何变换情况,B题目属于有标准答案型,没太大的把握不建议选择,可发挥型不大。 第一问 比较简单,就一个2维平面的问题,但有点没理解,这个…

学习笔记——Java入门第二季

1.1 介绍类与对象 类和对象的关系: 时间万物皆对象。对象是具体的事物,是类的具体事例 类是抽象的概念,是对象的模板。 new关键字是创建实例对象最重要的标志 Dog duoduonew Dog(); Dog luckynew Dog(); 这样就创建了两个对象并且在java内…

尚硅谷大数据项目《在线教育之离线数仓》笔记007

视频地址:尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 第12章 报表数据导出 P112 01、创建数据表 02、修改datax的jar包 03、ads_traffic_stats_by_source.json文件 P113 P114 P115 P116 P117 P118 P119 P120 P121 P122【122_在…

LeetCode每日一题:1123. 最深叶节点的最近公共祖先(2023.9.6 C++)

目录 1123. 最深叶节点的最近公共祖先 题目描述: 实现代码与解析: dfs 原理思路: 1123. 最深叶节点的最近公共祖先 题目描述: 给你一个有根节点 root 的二叉树,返回它 最深的叶节点的最近公共祖先 。 回想一下&…

钉钉消息已读、未读咋实现的嘞?

前言 一款app,消息页面有:钱包通知、最近访客等各种通知类别,每个类别可能有新的通知消息,实现已读、未读功能,包括多少个未读,这个是怎么实现的呢?比如用户A访问了用户B的主页,难道…

文字转语音TTS bark SpeechT5 mms

bark GitHub - suno-ai/bark: 🔊 Text-Prompted Generative Audio Model microsoft SpeechT5 https://github.com/microsoft/SpeechT5 使用 SpeechT5 进行语音合成、识别和更多功能 - 掘金 Facebook mms https://github.com/facebookresearch/fairseq/tree/mai…

私有化部署即时通讯平台,完美替代飞书和钉钉的SaaS系统

在当今快速发展的数字化时代,企业对于安全、灵活、可定制的即时通讯平台需求不断增长。作为一家领先的品牌,WorkPlus专注于提供私有化部署的即时通讯平台,完美替代飞书和钉钉的SaaS系统。本文将重点介绍WorkPlus如何通过创新的解决方案&#…

【C刷题训练营】第三讲(c语言入门训练)

前言: 大家好,我决定日后逐渐更新c刷题训练营的内容,或许能帮到入门c语言的初学者,如果文章有错误,非常欢迎你的指正! 💥🎈个人主页:​​​​​​Dream_Chaser~ 🎈&…

CSAPP的Lab学习——Archlab(Architecture Lab)

文章目录 前言一、A部分sum .ys:迭代求和链表元素写一个Y86-64的程序和。rsum .递归求和链表元素copy.ys 复制将源块复制到目标块 二、B部分三、C部分实现iaddq指令 总结 前言 一个本硕双非的小菜鸡,备战24年秋招。刚刚看完CSAPP,真是一本神…

C++信息学奥赛1190:上台阶

#include <iostream> using namespace std;long long arr[80]; // 用于存储斐波那契数列的数组int main() {int n;arr[1]1; // 初始化斐波那契数列的前三个元素arr[2]2;arr[3]4;for(int i4;i<71;i) { // 计算斐波那契数列的第4到第71个元素arr[i]arr[i-1]arr[i-2]…

【Linux权限管理】文件:毁灭我与我无关

一.预备知识 1.LInux用户分类 一台Linux机器的用户分为两类&#xff1a; 超级用户和普通用户。 注意我这里说的用户的并不是一个固定的人&#xff0c;例如你本身就有root账号&#xff0c;但你也可以使用自己创建普通账号。当你使用root账号时&#xff0c;你就是一个超级用户…

二叉查找树(binary search tree)(难度7)

C数据结构与算法实现&#xff08;目录&#xff09; 答案在此&#xff1a;二叉查找树&#xff08;binary search tree&#xff09;&#xff08;答案&#xff09; 写在前面 部分内容参《算法导论》 基本接口实现 1 删除 删除值为value的第一个节点 删除叶子节点1 删除叶子节…

android framework之Applicataion启动流程分析(四)

本文主要学习并了解Application的Activity启动流程。 这边先分析一下Launcher是如何启动进程的Acitivity流程。从Launcher启动Acitivity的时候&#xff0c;它是把启动任务丢给instrumentation模块去协助完成&#xff0c;由它进一步调用AMS的startActivity()方法 去启动&#xf…

怎么处理zk或redis脑裂

很极端场景会出现脑裂 什么是分布式的脑裂 怎么理解zk脑裂 就是ZK&#xff0c;与客户端可能因为网络原因&#xff0c;客户端A还在跑着后续程序&#xff0c;而zk与客户端之前的心跳断了&#xff0c;此zk就把这节点给删除了&#xff0c;这时另一个客户会加锁成功&#xff0c;就样…