G1如何解决跨代引用
一、解决跨代引用的前提
1.使用垃圾收集屏障
垃圾收集屏障(GC Barriers) ,与serail一致,在访问对象和修改对象时使用垃圾收集屏障,记录跨代引用,只是G1不再使用卡表来记录跨代引用,因为G1存在多个逻辑分区,与传统的收集器的1个老年代分区相比,G1拥有多个作为老年代的Region,一个卡表无法记录多个老年代的Region引用多个新生代Region的关系,所以G1引入了hash表的结构存储跨代引用。
二、存储跨代引用的Hash表
G1 垃圾收集器使用记忆集(Remembered Set,RSet) 来记录跨代引用关系,其核心是一个哈希表结构,用于高效存储和管理跨 Region 的引用信息。以下是其详细结构和工作原理:
1.hash表的结构
hash表的key为跨Region引用的引用方的Region地址,value是与serial相同结构的卡表,所以每个Region会记录所有其他Region对自己的引用。
┌─────────────────────────────────────────────────────────────────────┐
│ G1跨代引用哈希表(Hash Table) │
│ (键:老年代内存块标识;值:对应的卡表项,记录跨代引用信息) │
├───────────────┬───────────────┬───────────────┬───────────────┬─────┤
│ 哈希桶1 │ 哈希桶2 │ 哈希桶3 │ 哈希桶4 │ ... │
├───────────────┼───────────────┼───────────────┼───────────────┼─────┤
│ 键:OldGen块A │ 键:OldGen块B │ 键:OldGen块C │ 键:OldGen块D │ ... │
│ 值:卡表项A │ 值:卡表项B │ 值:卡表项C │ 值:卡表项D │ ... │
└───────────────┴───────────────┴───────────────┴───────────────┴─────┘
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 卡表项A │ │ 卡表项B │ │ 卡表项C │ │ 卡表项D │
├───────────────┤ ├───────────────┤ ├───────────────┤ ├───────────────┤
│ 脏标记:True │ │ 脏标记:False │ │ 脏标记:True │ │ 脏标记:False │
│ 内存块范围: │ │ 内存块范围: │ │ 内存块范围: │ │ 内存块范围: │
│ 0x1000-0x1200 │ │ 0x1200-0x1400 │ │ 0x1400-0x1600 │ │ 0x1600-0x1800 │
│ 跨代引用列表: │ │ 跨代引用列表: │ │ 跨代引用列表: │ │ 跨代引用列表: │
│ - 0x0200(Y) │ │ - 无 │ │ - 0x0300(Y) │ │ - 无 │
│ - 0x0250(Y) │ │ │ │ - 0x0350(Y) │ │ │
└───────────────┘ └───────────────┘ └───────────────┘ └───────────────┘
│ │
└─────────────────┬─────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 年轻代(Young Gen) │
│ (包含被老年代引用的对象:0x0200、0x0250、0x0300、0x0350) │
└─────────────────────────────────────────────────────────────┘
2.整体架构
classDiagram
class RSet {
-HashTable buckets
+add_ref(from_region, card_index)
+get_refs() : CardBitmap[]
}
class HashTable {
-Bucket[] table
-size : int
}
class Bucket {
-from_region_addr : address
-card_bitmap : CardBitmap
-next : Bucket
}
class CardBitmap {
-bits : byte[]
+set(card_index)
+test(card_index) : bool
}
RSet --> HashTable
HashTable --> Bucket
Bucket --> CardBitmap
3.关键组件说明
| 组件 | 大小 | 作用 |
|---|---|---|
| 哈希桶数组 | 2^n | 哈希表主结构,索引=来源Region地址哈希 |
| 桶项(Bucket) | 16-24字节 | 存储来源Region地址和卡片位图指针 |
| 卡片位图(CardBitmap) | RegionSize/CardSize/8 | 位图记录具体引用位置 |
4.引用记录流程
sequenceDiagram
写屏障->>RSet: add_ref(from_region, card_index)
RSet->>哈希表: 计算桶索引 = hash(from_region)
哈希表->>桶链: 查找匹配的Bucket
alt 存在匹配Bucket
桶链->>卡片位图: set(card_index)
else 不存在
哈希表->>新Bucket: 创建新项
新Bucket->>卡片位图: 初始化位图
卡片位图-->>桶链: set(card_index)
end
5.哈希冲突处理
graph TD
桶0 --> 项A[Region 0x1000]
桶0 --> 项B[Region 0x2000]
桶0 --> 项C[Region 0x3000]
桶1 --> 项D[Region 0x4000]
桶N --> 项E[Region 0x5000]
三、总结
G1是通过hash表不仅仅记录了老年代指向新生代的引用,同时也记录了新生代指向老年代的引用。解决了old gc需要完整扫描新生代的问题。但是多个Region的设计导致需要存在多个卡表,也造成了一定的空间占用。