G1如何给新建的对象选择Region

56 阅读3分钟

G1如何给新建的对象选择Region

我们已经了解了Region内部的结构和关键指针G1的Region的内部结构 ,接下来将介绍G1如何给对象选择Region。

一、Region如何给小对象分配内存

1.按策略选择region

2.使用指针碰撞分配空间

sequenceDiagram
    分配请求->>Region: 申请大小S
    Region->>Top: 读取当前值
    Top->>计算: Top + S
    计算->>"End": 比较End值
    alt 空间足够
        "End"-->>Region: 返回成功
        Region->>Top: 更新Top值
        Region-->>请求: 返回分配地址
    else 空间不足
        "End"-->>空闲链表: 调用空闲链表分配,剩余空间继续加入空闲链表
        空闲链表-->>Region: 返回碎片地址或失败
    end

二、G1如何给对象分配Region

1.Region管理结构

classDiagram
    class Region {
        +type: RegionType
        +top_ptr: address
        +end_ptr: address
        +free_list: FreeBlock*
        +bitmap: BitMap
        +prev: Region 逻辑顺序上一个region的指针
        +next: Region 逻辑顺序下一个region的指针
    }
    
    class RegionManager {
        +eden_list: List~Region~
        +survivor_list: List~Region~
        +old_list: List~Region~
        +humongous_list: List~Region~
        +free_list: List~Region~
    }
    
    RegionManager --> "*" Region

2.按规则选择不同类型的Region

G1 垃圾收集器使用多层筛选策略从 Region 列表中选择最适合存储新对象的 Region,其核心是价值最大化原则。以下是完整的 Region 选择机制:

graph TD
    A[对象分配请求] --> B{对象大小}
    B -->|小对象| C[选择Eden Region]
    B -->|大对象| D[选择Humongous Region]
    
    C --> E[Region筛选]
    D --> F[连续空间检查]
    
    E --> G[快速路径or空间相邻or价值评估]
    G --> H[最优选择]
    
    F --> I[空间连续性]
    I --> J[分配执行]
    
    style G white-space:normal

3.小对象分配:Eden Region 选择

sequenceDiagram
    G1分配器->>Region管理器: 请求Eden Region
    Region管理器->>筛选: 过滤可用Region
    筛选->>评估: 计算Region价值
    评估->>排序: 优先获取最近释放的region,<br/>如果没有就获取空间相邻的,<br/>如果失败其次是通过价值模型评估分数获取最高分
    排序-->>G1分配器: 返回最佳Region
    G1分配器->>TLAB: 从Region分配空间

源码实现(OpenJDK 17)

HeapWord* G1CollectedHeap::attempt_allocation(size_t size) {
  // 快速路径:尝试最近释放的Region
  HeapRegion* hr = _allocator->attempt_allocation_using_recent_region(size);
  if (hr != nullptr) {
    return hr->allocate(size);
  }
  
  // 次级路径:空间邻近Region
  hr = _allocator->attempt_allocation_near(size);
  if (hr != nullptr) {
    return hr->allocate(size);
  }
  
  // 慢速路径:完整价值模型
  return attempt_allocation_slow(size);
}

3.1 为什么实现多种策略

这就是理论模型与实际优化的差异,价值模型拥有最好的空间局部性 ,而快速路径拥有最好的分配速率,价值模型需要耗时计算所以在分配速率上低效,快速路径在实践中大概率是价值模型中的高分对象,也就是说,快速路径大概率拥有比较好的空间局部性。

graph TD
    A[空间局部性] --> B[定义]
    A --> C[价值模型]
    A --> D[快速路径]
    
    B --> B1["数据物理位置邻近性"]
    C --> C1["追求最优局部性"]
    D --> D1["追求最快分配"]

3.2设计哲学

graph LR
    优化目标 --> 时间[分配速度]
    优化目标 --> 精度[最优布局]
    
    矛盾 --> 方案[分层策略]
    方案 --> 快[快速路径保速度]
    方案 --> 精[价值模型保布局]

时间局部性原理

graph LR
    最近释放 --> 假设["刚释放的Region很可能再次使用"]
    假设 --> 依据[程序访问局部性]
    依据 --> 效果[缓存命中率提升]
style 假设 white-space:normal

空间局部性原理

sequenceDiagram
    程序访问->>内存: 访问对象A
    内存-->>缓存: 加载A及邻近区域
    后续访问->>缓存: 访问邻近对象B
    缓存-->>加速: 命中缓存

热度继承原理

gantt
    title Region热度继承
    dateFormat  s
    axisFormat %ss
    
    section RegionA
    高频分配 : 0, 3
    释放 : 3, 1
    
    section 新分配
    选择RegionA : 4, 1
    继承热度 : 5, 1

3.3 实践数据

策略触发频率平均延迟局部性得分
快速路径80%40ns0.85
空间邻近15%60ns0.92
价值模型5%150ns0.97

3.4 价值评估模型

image.png

image.png

3.5 region评估流程

sequenceDiagram
    participant 分配线程
    participant Region管理器
    participant NUMA管理器
    
    分配线程->>Region管理器: 请求新TLAB
    Region管理器->>筛选: 获取可用Eden Regions
    筛选-->>Region管理器: 返回候选列表
    
    Region管理器->>NUMA管理器: 查询线程NUMA节点
    NUMA管理器-->>Region管理器: 返回节点ID
    
    Region管理器->>计算: 计算每个Region得分
    计算->>公式: 得分=局部性×0.6 + NUMA×0.3 + 热度×0.1
    计算-->>Region管理器: 返回得分排序
    
    Region管理器->>选择: 选择最高分Region
    选择-->>分配线程: 返回Region地址

3.6 热度(Allocation Heat)

热度(Allocation Heat) 是一个关键指标,它衡量了 Region 在近期被用于对象分配的活跃程度。这个指标通过智能预测未来分配模式,优化新对象的分配位置。

graph TD
    A[分配热度] --> B[定义]
    A --> C[计算方式]
    A --> D[核心作用]
    
    B --> B1["Region被用于分配的频率"]
    C --> C1["基于历史分配次数"]
    D --> D1["预测未来分配热点"]

3.7 NUMA亲和度

在 G1 垃圾收集器中,NUMA 亲和度(NUMA Affinity) 是衡量内存 Region所在内存 与当前 CPU 节点的物理距离的指标,其计算涉及硬件拓扑感知和运行时动态绑定。非G1独有技术,详情见NUMA(Non-Uniform Memory Access,非一致内存访问) ,对于大内存多颗cpu的服务器具有明显优化。

3.8 小对象复用Region

需要注意的是,G1如果从空闲链表中,选择Region之后,会将Region从空闲链表移除,并标记为eden类型,然后加入到RegoinManger中的eden区列表中。

并不是所有小对象都会从空闲链表选择Region,或者说小对象的分配第一层,是先遍历所有eden类型的region,这个所有eden类型的region是G1顶层结构的一个列表,通过遍历eden列表寻找eden列表里面空闲的空间,找不到再去空闲链表通过策略去选择一个新的region作为eden区,并用于对象分配。

当新生代分配不了内存,g1也会使用大对象使用的区间树来分配内存。填充大对象分配和的region剩余部分。

4.大对象分配:Humongous Region 选择

4.1 连续空间选择算法

graph TD
    A[大对象分配] --> B{对象大小}
    B -->|小于等于 RegionSize| C[单个Region分配]
    B -->|大于 RegionSize| D[多Region分配]
    
    C --> E[查找空闲Region]
    D --> F[计算所需Region数]
    F --> G[查找连续空闲Region]
    
    E --> H[优先尾部Region]
    G --> I[尾部连续空间优先]
    
    H --> J[标记为Humongous]
    I --> K[标记为连续Humongous]

4.2 尾部优先策略

graph LR
    尾部优先 --> 原因[减少内存碎片]
    原因 --> 机制[优先使用堆尾部空间]
    机制 --> 优势[保持低地址空间连续]
    
    优势 --> 效果[减少Full GC触发]

4.3 连续空间查找算法

sequenceDiagram
    分配请求->>Region管理器: 申请大对象空间
    Region管理器->>空闲列表: 获取空闲Region列表
    空闲列表->>排序: 按地址降序排列
    排序->>查找: 从尾部向前扫描
    查找->>连续检查: 验证连续N个Region
    连续检查-->>Region管理器: 返回可用地址
    Region管理器-->>分配请求: 分配成功

4.4 单个 Region 分配

flowchart TD
    开始 --> 计算[计算所需Region数=1]
    计算 --> 查找[尾部优先查找空闲Region]
    查找 --> 分配[分配并标记为StartsHumongous]
    分配 --> 记录[加入全局Humongous列表]

这里对大对象的region分配不做详细讲解,详细请见G1如何管理空闲空间的