G1如何解决跨代引用

188 阅读3分钟

G1如何解决跨代引用

一、解决跨代引用的前提

1.使用垃圾收集屏障

垃圾收集屏障(GC Barriers) ,与serail一致,在访问对象和修改对象时使用垃圾收集屏障,记录跨代引用,只是G1不再使用卡表来记录跨代引用,因为G1存在多个逻辑分区,与传统的收集器的1个老年代分区相比,G1拥有多个作为老年代的Region,一个卡表无法记录多个老年代的Region引用多个新生代Region的关系,所以G1引入了hash表的结构存储跨代引用。

二、存储跨代引用的Hash表

G1 垃圾收集器使用记忆集(Remembered Set,RSet) 来记录跨代引用关系,其核心是一个哈希表结构,用于高效存储和管理跨 Region 的引用信息。以下是其详细结构和工作原理:

image.png

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的设计导致需要存在多个卡表,也造成了一定的空间占用。