JVM G1 回收器 RSet 与 PRT 深度解析
源码版本OpenJDK 8u HotSpotsrc/share/vm/gc_implementation/g1/关键词G1、Remembered Set、Per Region Table、写屏障、Card Table、粗化Coarsening1. 背景为什么 G1 需要 RSetG1Garbage-First是一款以Region为回收基本单位、追求可预测停顿的低延迟收集器。它把堆切成大量大小相等的HeapRegion默认约 2048 个每个 1MB~32MB回收时不再像传统分代收集器那样整代扫描而是只挑选若干region组成Collection SetCSet进行增量回收。这就带来一个核心难题增量回收某个 region 时如何在不扫描整个堆的前提下找到谁还在引用我如果 A region 里的对象被 B、C、D 等多个 region 引用回收 A 时就必须扫描这些外部引用者否则会把仍被引用的对象误回收。但全堆扫描违背了 G1 局部回收的初衷。解决办法是Remembered Set记忆集合RSet每个 region 维护一个 RSet记录有哪些来自其他 region 的指针指向了我。回收时只要扫描本 region 的 RSet 指向的少量 card就能精确找到外部引用而不必遍历整个堆。G1 的 RSet 并不记录对象级引用而是记录到card卡片粒度——card 是堆上一段固定大小默认 512 字节的内存块由 Card Table 管理。ptr card_shift即可把一个对象地址映射到它所属的 card。2. 引用变化如何通知 RSet写屏障对象的引用发生变化赋值如果不被感知RSet 就会失效。G1 通过写屏障Write Barrier拦截每一次引用字段的写入G1RemSet::write_ref_field / par_write_ref └─ g1RemSet.inline.hpp:69-72 HeapRegion* to _g1-heap_region_containing(obj); if (from ! to) { to-rem_set()-add_reference(p, tid); // 把p 指向 obj记入 obj 所在 region 的 RSet }要点被引用对象obj所在的 region 是被指向方toRSet 记在 to 上。from ! to说明这是一次跨 region的引用才需要进 RSetregion 内部引用不影响增量回收无需记录。tid是触发写入的线程 id用于并发与 FromCardCache 去重。3. 类层级HeapRegion → HeapRegionRemSet → OtherRegionsTableHeapRegion (heapRegion.hpp:211 _rem_set; 491 rem_set()) └─ HeapRegionRemSet (heapRegionRemSet.hpp:229, 内含 OtherRegionsTable _other_regions) └─ OtherRegionsTable (heapRegionRemSet.hpp:120, RSet 主体即下文PRT 容器) ├─ _sparse_table (SparsePRT稀疏) ├─ _fine_grain_regions (PerRegionTable** 开放哈希表细粒度 —— 源码真正的 PRT) └─ _coarse_map (BitMap粗粒度)术语澄清容易读混常见资料把OtherRegionsTable称为 “PRTPer Region Table”。但在 Oracle 这份源码里注释heapRegionRemSet.hpp:100-104明确写道_fine_grain_entries array is an open hash table of PerRegionTables (PRTs)——也就是说源码中真正的 PRT PerRegionTable类而OtherRegionsTable是PRT 的集合/管理器。本文用源码口径PRT PerRegionTable。HeapRegionRemSet本身是个薄壳所有实际记录工作都转交给OtherRegionsTableheapRegionRemSet.hpp:307-314。4. OtherRegionsTable 的三级存储OtherRegionsTable要回答的唯一问题是“哪些来自其他 region 的card 含有指向我owner region的指针”它用三级粒度来回答本质是在记录精度与内存占用之间做权衡引用越稀疏越省空间引用越密集越追求精确。4.1 Coarse粗粒度——_coarse_map结构一整张BitMapheapRegionRemSet.hpp:128位数 整个堆的 region 总数。存储的值只有1 bit 一个 region 索引。第i位为 1 表示regioni可能包含指向我的指针不记录是哪个 card。写入命中即跳过if (_coarse_map.at(from_hrm_ind)) return;heapRegionRemSet.cpp:457。置位动作发生在 fine 表满、淘汰 PRT 时delete_region_table()把被淘汰 PRT 对应的 region 在_coarse_map中置 1cpp:622-625称为粗化coarsen。性能✅ 查询 O(1)一次 bit 测试内存极小N 个 region 只花 N bit是所有方案里最省空间的兜底。❌ 代价最大GC 扫描时因为不知道具体 card必须扫描整个引用者 region 的所有对象/card扫描成本最高。用_n_coarse_entries统计粗化数量。4.2 Fine细粒度—— PerRegionTable源码真正的 PRT结构_fine_grain_regions是PerRegionTable**数组heapRegionRemSet.hpp:132即开放哈希表 冲突链。长度_max_fine_entriescpp:284-286 2^log2(G1RSetRegionEntries)默认基数 256再乘region_size_log_mb1。哈希ind from_hrm_ind _mod_max_fine_entries_mask冲突用_collision_list_next串成链。每个PerRegionTable存什么heapRegionRemSet.cpp:41_hr引用者 region 指针_bmBitMap长度 CardsPerRegion一个 region 内的 card 总数每一位对应引用者 region 内的一个 card存储的值把该引用者 region 内哪些 card 含指向 owner 的指针编码进 bit 位。add_reference_workcpp:89把from转成相对该 regionbottom()的 card 偏移hw_offset (card_shift - LogHeapWordSize)再add_card_work置位_bm对应 bit并行用par_at_put 原子自增_occupied。写入/查询prt-add_reference(from)置位contains_reference直接return _bm.at(card_ind)。性能✅ 最精确GC 只扫被置位的少数 card而非整个 region扫描成本最低。内存每个 PRT 固定带一张CardsPerRegion位的 BitMap本身不便宜但数量有硬上限默认 256 量级总体有界。上限与淘汰粗化fine 表满_n_fine_entries _max_fine_entries触发delete_region_table()cpp:578在采样窗口_fine_eviction_stride/_fine_eviction_sample_size里挑选occupied 最大的 PRT把它对应的 region 粗化置 coarse bit并立即init复用该 PRT 给新 regioncpp:509。即牺牲精度换内存有界。查找无锁、改桶链才持_mPRT 通过静态_free_list对象池复用cpp:206-203避免频繁 new/delete。遍历高效_first_all_fine_prts/_last_all_fine_prts双向链表批量操作所有 fine PRT 时不必扫整个哈希表。4.3 Sparse稀疏——_sparse_tableSparsePRT为什么存在当一个引用者 region 只贡献了极少 card时为它建一张整 BitMap 的 PRT 太浪费先用紧凑结构表示。结构SparsePRT内含两张RSHashTable_cur/_next双缓冲sparsePRT.hpp:216-217。每张RSHashTable_buckets桶数组容量为 2 的幂用capacity_mask()取模哈希_entriesSparsePRTEntry连续数组变长对象桶内用SparsePRTEntry::_next_index串成冲突链。SparsePRTEntry存什么sparsePRT.hpp:45_region_ind引用者 region 索引_cards[1]变长数组存该 region 内、含指向 owner 指针的 card 偏移相对 region bottom最多cards_num()个cards_num()MAX2(G1RSetSparseRegionEntries ~(4-1), 4)sparsePRT.hpp:64默认基数G1RSetSparseRegionEntriesBase 4所以每个 entry 默认最多存 4 个 card且数量恒为 4 的倍数配合UnrollFactor 4循环展开。存储的值(引用者 region 索引 → 至多 4 个 card 偏移)的紧凑映射。写入/溢出add_card在 entry 的_cards里找空位放入sparsePRT.cpp:93带 4 路展开若 4 个槽都满 → 返回overflow→ 上层在OtherRegionsTable::add_reference走建/复用 PRT 把 sparse 卡迁过去分支heapRegionRemSet.cpp:528-541即sparse → fine 升级promote。性能✅ 最省内存只有真正有引用的 region 才占 entry且每个 entry 仅存几个 int 级 card 偏移而非整张 BitMap。哈希表初始容量仅 16InitialCapacitysparsePRT.hpp:222利用率 50%occupied_entries*2 capacity自动expand()sparsePRT.cpp:483。双缓冲_cur/_nextsparsePRT.hpp:213-217 注释GC 扫描迭代只读_cur新增/删除走_next从而支持unsynchronized reads/iterations并发读不被并发改破坏cleanup 阶段再 reconcile。循环展开UnrollFactor4加速_cards的查找与拷贝。❌ 代价查找需哈希 冲突链遍历比 coarse 慢单 entry card 上限小超过即升级到 fine用空间换精度。5. 升级路径与总体调度引用少 ── Sparse (每引用者 region 至多 4 个 card 偏移极省内存) │ 某 region 存满 4 个 card 再插第 5 个 → overflow ▼ 引用增多 ── Fine / PerRegionTable (整张 card 位图精确扫描最省) │ fine 表达上限 (_max_fine_entries默认 ~256) ▼ 过多引用者 ── Coarse (1 bit/region牺牲扫描精度保住内存上限)sparse → fine单 entry 4 个 card 溢出触发 promote并把已存的 sparse 卡迁入新建 PRT。fine → coarsefine 表满淘汰 occupied 最大的 PRT 并粗化cpp:578。整体策略是能省则省、逐级退让动态平衡记录开销与GC 扫描开销。6. 跨三级的通用加速FromCardCache 去重FromCardCacheheapRegionRemSet.hpp:50每线程 × 每 owner region 缓存最近处理的 card 索引是横跨三层的优化。add_reference一进来先查它FromCardCache::contains_or_replace(tid, cur_hrm_ind, from_card)cpp:444——同一线程、同一 owner region、同一 card 刚处理过就直接返回避免重复入表。这在高频赋值的写密集场景下显著减少重复工作。7. 并发与内存序OtherRegionsTable头部注释heapRegionRemSet.hpp:106-118明确其并发策略查找 PRT 可以无锁find_region_table但修改桶链必须持_mMutex。允许并发读到正在被删除/复用的 PRT这是安全的因为PRT 只会在安全点真正释放平时是被复用cpp:113删除 PRT 时会先置coarse_map的 bit所以漏记不了复用 PRT 时即使误加 bit 也无害cpp:116-118。add_reference_work用is_in_reserved_raw防御并发复用cpp:109新建 PRT 用OrderAccess::release_store_ptr发布cpp:525保证内容可见后才对外可用。8. 小结粒度存储结构存的值查询内存扫描精度Coarse1 bit/region_coarse_mapregion 索引可能存在O(1)极小必须扫整个引用者 regionFine / PRTPerRegionTable card 位图引用者 region 内具体 card 偏移O(1) 位测试中数量有上限只扫置位 card最精确SparseSparsePRT(region→≤4 card) 紧凑哈希region 索引 至多 4 个 card 偏移哈希链极小只扫记录的 cardG1 用这套能省则省、逐级退让的三级存储在记录成本维护 RSet 的写屏障开销、内存与GC 扫描成本回收时遍历 RSet 的开销之间取得了动态平衡这正是 RSet 能支撑 G1 增量、可预测停顿回收的关键设计。附关键源码位置速查内容文件:行HeapRegion::rem_set()heapRegion.hpp:491HeapRegionRemSet类heapRegionRemSet.hpp:229OtherRegionsTable类 / 三级字段heapRegionRemSet.hpp:120, 128/132/149add_reference主流程heapRegionRemSet.cpp:425delete_region_table粗化heapRegionRemSet.cpp:578PerRegionTable类 /add_reference_workheapRegionRemSet.cpp:41 / 89SparsePRT/SparsePRTEntrysparsePRT.hpp:210 / 45写屏障入口to-rem_set()-add_referenceg1RemSet.inline.hpp:72FromCardCache去重heapRegionRemSet.cpp:444

相关新闻

RAG+Agent主程序架构设计:从核心原理到生产级实现

RAG+Agent主程序架构设计:从核心原理到生产级实现

1. 从概念到现实:为什么RAGAgent是当前AI应用的核心范式如果你最近在关注AI应用开发,尤其是想构建一个能真正“理解”并“使用”你私有数据的智能助手,那么“RAGAgent”这个组合你一定绕不开。它不再是实验室里的概念,而是正在成为…

2026/10/14 3:05:16 阅读更多 →
XXL-Job双实例重复执行问题排查:从数据库主键失效到时钟同步陷阱

XXL-Job双实例重复执行问题排查:从数据库主键失效到时钟同步陷阱

最近在排查一个线上任务重复执行的问题,客户反馈明明配置了单机串行执行,但日志里却出现了同一任务在同一时间被两个不同的执行器实例执行了两次。这直接导致了数据重复处理,下游系统报错。问题指向了分布式任务调度框架 XXL-Job,…

2026/10/14 3:05:09 阅读更多 →
AI前沿日报_2026-08-13

AI前沿日报_2026-08-13

🤖 AI 前沿日报:2026年8月13日三大模型同日发布:Qwen 3.8开源2.4万亿参数MoE、Grok 4.6对标GPT-5.6、DeepSeek V4 Pro上线API;AI代理自主发现开源项目安全漏洞并获人类维护者合并;OpenAI伦理负责人入职不到一年辞职&am…

2026/10/7 6:48:38 阅读更多 →

最新新闻

问题:ubuntu与linux开发板互ping

问题:ubuntu与linux开发板互ping

一、问题原理要解决ubuntu与开发板互ping的问题,就是要使ubuntu和开发板在同一个网段上面。本次问题解决环境:开发板 和 windows 均通过wifi的方式连接网络。二、解决方法1、首先将ubuntu设置成桥接模式状态栏上方点击 虚拟机,点击 设置&…

2026/10/14 3:05:32 阅读更多 →
2026年全国3D溜溜网云渲染速度表现及选购参考指南

2026年全国3D溜溜网云渲染速度表现及选购参考指南

云渲染服务核心评估维度与行业标准当前3D设计领域数字化服务需求持续增长,云渲染服务已成为设计师提升出图效率的核心工具,不同平台的服务能力差异较大,明确统一的评估标准可帮助用户降低选型成本。对于同时承接3个家装项目、自有设备单张4K效…

2026/10/14 3:05:32 阅读更多 →
Chat2DB:开源AI数据库终端,自然语言转SQL的工程实践

Chat2DB:开源AI数据库终端,自然语言转SQL的工程实践

简介:Chat2DB是一款面向软件开发者的AI驱动型开源数据库管理工具,专为降低SQL使用门槛而设计,特别适合非数据库专家、初级开发者及数据分析师快速开展数据库查询、分析与迁移工作。资源包为项目源码压缩包(zip格式)&am…

2026/10/14 3:05:32 阅读更多 →
MySQL索引优化:从B+树原理到慢查询实战

MySQL索引优化:从B+树原理到慢查询实战

做后端开发这几年,最常被拉到群里去救火的场景,十有八九是数据库慢查询。系统跑得好好的,突然某天接口变慢、CPU飙高,一看日志全是SELECT走了全表扫描,几百万行的表一次查询几秒钟才回来。这种时候,十次里有…

2026/10/14 3:05:32 阅读更多 →
RGF_CJ 全局函数全解析:从应用生命周期、系统定时器到窗口与指针互操作

RGF_CJ 全局函数全解析:从应用生命周期、系统定时器到窗口与指针互操作

图形学桌面应用 【免费下载链接】RGF_CJ RGF是仓颉Windows系统下的通用渲染框架(兼应用开发框架)。RGF仓颉版(后续简称"RGF")基于RGF(C/C版)封装优化而来。RGF为开发者提供轻量化、安全、高性能以及高度一致性的2D渲染能…

2026/10/14 3:05:32 阅读更多 →
Python爬虫实战:手机APP数据抓取与解析全流程

Python爬虫实战:手机APP数据抓取与解析全流程

简介:这份PDF资料面向具备Python基础、对网络请求与爬虫开发感兴趣的技术爱好者与研发工程师,围绕手机APP数据传输的抓取与解析展开,重点讲解如何借助Fiddler抓包定位登录与话题接口,再通过urllib2构造HTTP请求、携带Header与表单…

2026/10/14 3:04:31 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →