G1的Region的内部结构
Region的内部结构
在 G1 垃圾收集器中,每个 Region 内部采用多种指针协同完成内存分配与管理。以下是 Region 内部指针结构的详细解析:
一、核心指针详解
核心指针的作用在于region给对象分配空间的时候,使用指针碰撞的方式,移动Top指针来分配空间。
2.Top 指针(分配位置指针)
内存布局示意
diff
Region内存布局:
+--------------------------------+
| 已分配对象1 | 已分配对象2 | ... |
+--------------------------------+
↑ ↑
Base Top (下一个对象将从这里开始)
2.End 指针(区域边界指针)
用来检查内存分配是否会超过界限
与 Top 指针关系
css
0x0000 ┌──────────────┐ ← Base
│ 已用空间 │
├──────────────┤ ← Top
│ 空闲空间 │
├──────────────┤ ← End
│ 元数据 │
0x1FFF └──────────────┘
2.辅助指针结构(仅old类型的region管理Block)
在G1中,region刚开始都是连续的可用空间,但随着对象的回收,会使连续的空间变成碎片的空间(G1对于低价值的old region会直接将死亡对象标记为碎片,不会直接把空间清空,省去了一个操作,新对象可以直接覆盖这个空间的数据),碎片会通过Block结构体来描述,当指针碰撞分配失败就会尝试在空闲块寻找空间。每个Block都有上一个和下一个Block的指针。只有类型为old的region才会使用碎片管理,eden类型的region只有核心指针。
1. Block 指针
每个空闲块都是通过Block结构体来管理的,Block会有上一个和下一个Block的指针。
2. Prev/Next 指针(Block)
3.空闲块结构图示
lua
+-----------------+ +-----------------+
| size | prev |----->| size | prev |
| (8B) | (8B) |<-----| (8B) | (8B) |
+-----------------+ +-----------------+
| next | 空闲空间 | | next | 空闲空间 |
| (8B) | | | (8B) | |
+-----------------+ +-----------------+
↑ ↑
空闲块头 空闲块头
4.空间分隔与边界标记
2.块类型(已用/空闲)
3.前驱/后继块指针"] 实际内存布局 --> 图示[" +---------------------+ | Block 头 (16字节) | ← Block指针位置 | - size = 128B | | - type = USED | | - next = 0x0000 | +---------------------+ | 对象数据 (112字节) | +---------------------+ "] style 图示 white-space:normal
5. 空闲块链表维护
6.指针可视化关系
less
Region 0x1000
┌────────────────────────────┐
│ Region 头部 (16字节) │
│ - top = 0x1100 │ ← 唯一位置指针
│ - end = 0x2000 │
│ - free_head = 0x1200 │ ← 唯一链表头指针
├────────────────────────────┤
│ 连续空间 (256字节) │ ← 受top指针管理
├────────────────────────────┤
│ 空闲块1 (Block @0x1200) │
│ - size=128B, next=0x1400 │ ← 独立Block指针①
├────────────────────────────┤
│ 碎片空间 (128字节) │
├────────────────────────────┤
│ 空闲块2 (Block @0x1400) │
│ - size=64B, prev=0x1200 │ ← 独立Block指针②
├────────────────────────────┤
│ 碎片空间 (64字节) │
└────────────────────────────┘
三、元数据指针
1**. BitMap 指针(存活标记)**
Region 中的 Bitmap 指针主要用于标记对象的存活状态,是实现并发标记和快速内存回收的核心数据结构。以下是其具体作用和工作机制:
2. Bitmap 的核心作用
1.对象存活标记
G1 将堆内存划分为多个大小相等的 Region(默认 1-32MB),每个 Region 都关联一个位图(Bitmap),用于标记其中对象的存活状态:
- 标记位值 :
0
:对象已死亡(可回收)。1
:对象存活(需保留)。
- 标记时机 :在并发标记阶段,G1 通过三色标记算法遍历对象图,使用 Bitmap 记录每个对象的可达性状态。
2.快速内存回收
Bitmap 让 G1 在回收 Region 时无需遍历所有对象,只需扫描 Bitmap 即可快速识别哪些内存区域可直接释放,显著提升回收效率。
3.实现原理
例如:若 Region 大小为 2MB,粒度单位为 512 字节,则该 Region 包含 2MB / 512B = 4096
个粒度单位,对应 Bitmap 需要 4096 bit = 512字节
来存储标记信息
当需要标记一个对象时,首先计算其在 Region 内的内存地址,然后通过地址偏移量确定它属于哪个 "粒度单位":
- 公式:
粒度单位索引 = (对象地址 - Region起始地址) / 粒度单位大小
- 例如:对象地址为
0x1000800
,Region 起始地址为0x1000000
,粒度单位为 512 字节,则偏移量为0x800
(2048 字节),对应索引为2048 / 512 = 4
,即该对象属于第 4 个粒度单位。
然后找到Bitmap的第四位bit,改为1,表示这个区域至少有一个对象存活。这与卡表的思路相似
3.RSet指针
RSet指针指向的是一个hash表,目的是记录跨Region的引用,或者说解决跨带引用,详情可以见G1如何解决跨代引用
四、连接指针
G1将Region划分成默认的2048个,也就是说region按顺序可以分为编号为1到2048,G1会将Region按照编号顺序连接起来假设Region15的prev指针,指向但就是Region14。
1.prev指针
指向上一个逻辑编号Region头的指针
2.next指针
指向下一个逻辑编号Region头的指针