G1的Region的内部结构

G1的Region的内部结构

Region的内部结构

在 G1 垃圾收集器中,每个 Region 内部采用多种指针协同完成内存分配与管理。以下是 Region 内部指针结构的详细解析:

graph TD A[Region 内存结构] --> B[核心指针] A --> C[辅助指针] A --> D[元数据指针] A --> E[连接指针] B --> B1["Top指针(位置指针)"] B --> B2["End指针(边界指针)"] C --> C1["Prev指针(前驱指针)"] C --> C2["Next指针(后继指针)"] C --> C3["Block指针(块结构指针)"] D --> D1["BitMap指针(标记位图)"] D --> D2["RSet指针(引用记录)"] E --> E1["Region * prev指针,指向上一个Region头的指针"] E --> E2["Region * next指针,指向下一个Region头的指针"] style E1 white-space:normal style E2 white-space:normal

一、核心指针详解

核心指针的作用在于region给对象分配空间的时候,使用指针碰撞的方式,移动Top指针来分配空间。

2.Top 指针(分配位置指针)

graph LR Top[Top指针] --> 作用["指向下一个可分配空间的起始地址"] Top --> 分配["对象分配时:newAddress = Top"] Top --> 更新["分配后:Top += objectSize"] Top --> 特征["连续分配时O(1)时间复杂度"] style 分配 white-space:normal style 作用 white-space:normal

内存布局示意

diff 复制代码
Region内存布局:
+--------------------------------+
| 已分配对象1 | 已分配对象2 | ...    |
+--------------------------------+
↑                        ↑
Base         Top (下一个对象将从这里开始)

2.End 指针(区域边界指针)

用来检查内存分配是否会超过界限

sequenceDiagram GC线程 ->> "end"指针 : 获取Region边界 "end"指针 ->> 分配器 : 返回Region结束地址 分配器 ->> 检查 : Top + size <= 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的指针。

graph TD subgraph Region 内存布局 A[Block指针结构] --> B[头部元数据] A --> C[对象存储区] A --> D[空闲块链表] B --> B1["Block 类型标记"] B --> B2["存活对象位图"] D --> D1["空闲块 A"] D --> D2["空闲块 B"] end subgraph Block指针核心功能 E[空间管理] --> F["标记连续空间边界"] E --> G["连接空闲块形成链表"] H[对象追踪] --> I["定位对象起始位置"] H --> J["快速计算对象大小"] end

2. Prev/Next 指针(Block)

graph LR style 管理 white-space:normal style 分配 white-space:normal 空闲块 --> 结构["struct FreeBlock { size_t size; FreeBlock* prev; FreeBlock* next; }"] 结构 --> 管理["连接所有空闲内存块形成链表"] 管理 --> 分配["分配时遍历链表寻找合适空间"] 管理 --> 合并["相邻空闲块自动合并"]

3.空闲块结构图示

lua 复制代码
+-----------------+      +-----------------+
| size  | prev    |----->| size  | prev    |
| (8B)  | (8B)    |<-----| (8B)  | (8B)    |
+-----------------+      +-----------------+
| next  | 空闲空间 |      | next  | 空闲空间 |
| (8B)  |        |      | (8B)  |        |
+-----------------+      +-----------------+
       ↑                      ↑
      空闲块头               空闲块头

4.空间分隔与边界标记

graph LR Block指针 -->|指向| 内存块起始位置 Block指针 --> 包含数据["1.内存块大小
2.块类型(已用/空闲)
3.前驱/后继块指针"] 实际内存布局 --> 图示[" +---------------------+ | Block 头 (16字节) | ← Block指针位置 | - size = 128B | | - type = USED | | - next = 0x0000 | +---------------------+ | 对象数据 (112字节) | +---------------------+ "] style 图示 white-space:normal

5. 空闲块链表维护

sequenceDiagram GC线程->>空闲链表: 释放死亡对象 空闲链表->>Block指针: 将连续空间转为空闲块 Block指针->>空闲链表: 更新前驱(next)和后继(prev)指针 空闲链表-->>新分配: 提供可用块地址

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 指针主要用于标记对象的存活状态,是实现并发标记和快速内存回收的核心数据结构。以下是其具体作用和工作机制:

graph TD BitMap["BitMap指针"] --> 位置["指向单独内存区域"] BitMap --> 结构["每bit对应2字节内存(512bit/Region)"] BitMap --> 更新["GC标记阶段设置位标记"] BitMap使用 --> 查询["快速查询对象存活状态"] BitMap使用 --> 清理["GC后重置位图"]

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头的指针

相关推荐
最后的自由12 小时前
hashcode方法导致的优化失效
jvm
最后的自由12 小时前
Mark Word 位分配与年龄位压缩的真相
jvm
最后的自由12 小时前
Region 大小和数量
jvm
最后的自由14 小时前
java对象的内存布局
jvm
最后的自由14 小时前
jvm 对象空间分配机制深度解析:指针碰撞 vs 空闲链表
jvm
最后的自由15 小时前
jvm虚拟机的组成部分
jvm
LZQqqqqo15 小时前
C# 析构函数
jvm
乘风破浪~~15 小时前
JVM对象创建与内存分配机制
jvm
℡余晖^15 小时前
每日面试题11:JVM
jvm