OpenJDK16 ZGC 源码分析(二)对象分配

1. 简介

自从JDK10中的引入了JEP 304: Garbage Collector Interface 后,OpenJDK定义了一整套关于GC的虚方法,供具体的GC算法实现。极大了简化了开发难度和代码的可维护性。

JEP 304定义了CollectedHeap类,每个GC都需要实现。CollectedHeap类负责驱动HotSpot的GC,以及和其他模块的交互。GC应当实现如下功能:

  • CollectedHeap的子类
  • BarrierSet集合类的实现,提供在运行时各种屏障功能。
  • CollectorPolicy类的实现
  • GCInterpreterSupport的实现,提供GC在解释执行时各种屏障功能(使用汇编指令)
  • GCC1Support的实现, 提供GC在C1编译代码中各种屏障功能
  • GCC2Support的实现, 提供GC在C2编译代码中各种屏障功能
  • 最终GC指定参数的初始化
  • 一个MemoryService,提供内存池、内存管理等

通常的,对象分配的入口在InstanceKlass::allocate_instance,该方法调用heap->obj_allocate()进行分配。

instanceOop InstanceKlass::allocate_instance(TRAPS) {
  bool has_finalizer_flag = has_finalizer(); // Query before possible GC
  int size = size_helper();  // Query before forming handle.

  instanceOop i;

  i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL);
  if (has_finalizer_flag && !RegisterFinalizersAtInit) {
    // 对于实现了finalize方法的类的实例的特殊处理
    i = register_finalizer(i, CHECK_NULL);
  }
  return i;
}

2. CollectedHeap对象分配流程图

对象分配一般如下流程:
在这里插入图片描述

3. 源码分析

3.1 ZCollectedHeap

ZCollectedHeap重载了CollectedHeap的方法,其中包含了对象分配的相关方法。而核心逻辑在放在ZHeap中。ZCollectedHeap中主要的成员方法如下:

class ZCollectedHeap : public CollectedHeap {
  friend class VMStructs;

private:
  // 软引用清理策略
  SoftRefPolicy     _soft_ref_policy;
  // 内存屏障,解释执行/C1/C2执行时对象访问的屏障
  ZBarrierSet       _barrier_set;
  // 初始化逻辑
  ZInitialize       _initialize;
  // 堆管理的核心逻辑,包括对象分配、转移、标记
  ZHeap             _heap;
  // 垃圾回收线程,触发
  ZDirector*        _director;
  // 垃圾回收线程,执行
  ZDriver*          _driver;
  // 垃圾回收线程,统计
  ZStat*            _stat;
  // 工作线程
  ZRuntimeWorkers   _runtime_workers;
}

3.2 ZHeap

ZHeap是ZGC内存管理的核心类。主要变量如下:

class ZHeap {
  friend class VMStructs;

private:
  static ZHeap*       _heap;
  // 工作线程
  ZWorkers            _workers;
  // 对象分配器
  ZObjectAllocator    _object_allocator;
  // 页面分配器
  ZPageAllocator      _page_allocator;
  // 页表
  ZPageTable          _page_table;
  // 转发表,用于对象迁移后的指针映射
  ZForwardingTable    _forwarding_table;
  // 标记管理
  ZMark               _mark;
  // 引用处理器
  ZReferenceProcessor _reference_processor;
  // 弱引用处理器
  ZWeakRootsProcessor _weak_roots_processor;
  // 转移管理器,用于对象迁移(类比G1的疏散)
  ZRelocate           _relocate;
  // 转移集合
  ZRelocationSet      _relocation_set;
  // 从元空间卸载类
  ZUnload             _unload;
  ZServiceability     _serviceability;
}

3.3 对象分配器

对象分配的主要逻辑在ZObjectAllocator。

3.3.1 对象分配器主要变量

ZObjectAllocator的主要变量如下:

class ZObjectAllocator {
private:
  const bool         _use_per_cpu_shared_small_pages;
  // 分CPU记录使用内存size
  ZPerCPU<size_t>    _used;
  // 分CPU记录undo内存size
  ZPerCPU<size_t>    _undone;
  // 缓存行对齐的模板类
  ZContended<ZPage*> _shared_medium_page;
  // 按CPU从缓存分配对象
  ZPerCPU<ZPage*>    _shared_small_page;
}

3.3.2 分配方法

对象分配的核心方法是alloc_object

uintptr_t ZObjectAllocator::alloc_object(size_t size, ZAllocationFlags flags) {
  if (size <= ZObjectSizeLimitSmall) {
    // Small
    return alloc_small_object(size, flags);
  } else if (size <= ZObjectSizeLimitMedium) {
    // Medium
    return alloc_medium_object(size, flags);
  } else {
    // Large
    return alloc_large_object(size, flags);
  }
}
  • 按对象的size,决定调用small page分配、medium page分配还是large page分配。
  • 分配入参除了size外,还有个ZAllocationFlags。ZAllocationFlags是个8bit的配置参数。

large page分配方法如下:

uintptr_t ZObjectAllocator::alloc_large_object(size_t size, ZAllocationFlags flags) {
  uintptr_t addr = 0;

  // 对齐2MB
  const size_t page_size = align_up(size, ZGranuleSize);
  // 分配页面
  ZPage* const page = alloc_page(ZPageTypeLarge, page_size, flags);
  if (page != NULL) {
    // 在页面中分配对象
    addr = page->alloc_object(size);
  }

  return addr;
}
  • small page分配和medium page分配都会调用到alloc_object_in_shared_page方法
  • 小对象和中对象的分配略有不同,小对象是根据所在CPU从共享页面中分配对象。而中对象则是全部线程共享一个medium page。
// shared_page:页面地址
// page_type:page类型,small还是medium
// page_size: page size
// size: 对象size
// flags: 分配标识
uintptr_t ZObjectAllocator::alloc_object_in_shared_page(ZPage** shared_page,
                                                        uint8_t page_type,
                                                        size_t page_size,
                                                        size_t size,
                                                        ZAllocationFlags flags) {
  uintptr_t addr = 0;
  // 获取一个page
  ZPage* page = Atomic::load_acquire(shared_page);

  if (page != NULL) {
    // 调用page的分配对象方法
    addr = page->alloc_object_atomic(size);
  }

  if (addr == 0) {
    // 如果刚才没有获取page成功,则分配一个new page
    ZPage* const new_page = alloc_page(page_type, page_size, flags);
    if (new_page != NULL) {
      // 先分配对象,然后加载page到shared_page缓存
      addr = new_page->alloc_object(size);

    retry:
      // 加载page到shared_page缓存
      ZPage* const prev_page = Atomic::cmpxchg(shared_page, page, new_page);
      if (prev_page != page) {
        if (prev_page == NULL) {
          // 如果prev_page已经淘汰,则goto到retry一直重试
          page = prev_page;
          goto retry;
        }

        // 其他线程加载了页面,则使用prev_page分配
        const uintptr_t prev_addr = prev_page->alloc_object_atomic(size);
        if (prev_addr == 0) {
          // 如果分配失败,则goto到retry一直重试
          page = prev_page;
          goto retry;
        }

        addr = prev_addr;
        undo_alloc_page(new_page);
      }
    }
  }

  return addr;
}

3.4 Page内的对象分配

page内的对象分配主要是两个方法alloc_object_atomic和alloc_object,其中alloc_object没有锁竞争,主要用于新page的第一次对象分配。

先看alloc_object_atomic

inline uintptr_t ZPage::alloc_object_atomic(size_t size) {
  assert(is_allocating(), "Invalid state");

  // 对象对齐,默认8字节对齐
  const size_t aligned_size = align_up(size, object_alignment());
  uintptr_t addr = top();

  for (;;) {
    const uintptr_t new_top = addr + aligned_size;
    if (new_top > end()) {
      // page没有申昱空间,则返回0
      return 0;
    }

    // cas操作更新prev_top指针
    const uintptr_t prev_top = Atomic::cmpxchg(&_top, addr, new_top);
    if (prev_top == addr) {
      // 调用ZAddress::good获取colored pointer
      return ZAddress::good(addr);
    }

    // 无限重试
    addr = prev_top;
  }
}

在看看alloc_object

inline uintptr_t ZPage::alloc_object(size_t size) {
  assert(is_allocating(), "Invalid state");

  // 对象空间对齐,默认8字节
  const size_t aligned_size = align_up(size, object_alignment());
  const uintptr_t addr = top();
  const uintptr_t new_top = addr + aligned_size;

  if (new_top > end()) {
    // 剩余空间不足,返回0
    return 0;
  }

  _top = new_top;
  // 调用ZAddress::good获取colored pointer
  return ZAddress::good(addr);
}

3.5 Colored pointer的计算

可以看到上述两个方法在分配结束都调用了ZAddress::good返回colored pointer。看看ZAddress::good的实现。

inline uintptr_t ZAddress::offset(uintptr_t value) {
  return value & ZAddressOffsetMask;
}

inline uintptr_t ZAddress::good(uintptr_t value) {
  return offset(value) | ZAddressGoodMask;
}

void ZAddress::set_good_mask(uintptr_t mask) {
  ZAddressGoodMask = mask;
  ZAddressBadMask = ZAddressGoodMask ^ ZAddressMetadataMask;
  ZAddressWeakBadMask = (ZAddressGoodMask | ZAddressMetadataRemapped | ZAddressMetadataFinalizable) ^ ZAddressMetadataMask;
}
  • good方法其实挺简单,先取4位染色值,然后或操作实际地址,获取colored pointer。
  • colored pointer将在load barrier中使用,后文将详细介绍load barrier机制。

4. 总结

本文介绍了ZGC中对象分配的核心代码和流程,下篇文章将继续介绍读屏障load barrier机制。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值