OpenJDK16 ZGC 源码分析(五)GC阶段之并发标记

1. 简介

并发标记是ZGC的第2个阶段,此阶段中,GC worker与Mutator并发工作,GC worker执行标记、load barrier和relocate。

并发线程从带标记对象列表开始,递归遍历对象及对象的成员变量并标记。

2. 源码分析

2.1 入栈

标记栈的size受ZMarkStackSpaceLimit参数控制,默认8G,合法值32MB到1TB之间。
share/gc/z/z_globals.hpp

  product(size_t, ZMarkStackSpaceLimit, 8*G,                               
          "Maximum number of bytes allocated for mark stacks")             
          range(32*M, 1024*G)                                              

标记栈元素分为两种:

  • 如果是普通对象,则直接入栈、递归标记即可。
  • 如果是数组对象,全部入栈则可能引起溢出,需判断数组大小是否超过ZMarkPartialArrayMinSize(默认4KB),未超过则入栈标记,否则按ZMarkPartialArrayMinSize切分后入栈标记。
    share/gc/z/zMarkStackEntry.hpp
  ZMarkStackEntry(uintptr_t object_address, bool follow, bool finalizable) :
      _entry(field_object_address::encode(object_address) |
             field_follow::encode(follow) |
             field_partial_array::encode(false) |
             field_finalizable::encode(finalizable)) {}

  ZMarkStackEntry(size_t partial_array_offset, size_t partial_array_length, bool finalizable) :
      _entry(field_partial_array_offset::encode(partial_array_offset) |
             field_partial_array_length::encode(partial_array_length) |
             field_partial_array::encode(true) |
             field_finalizable::encode(finalizable)) {}

share/gc/z/zMark.cpp

void ZMark::push_partial_array(uintptr_t addr, size_t size, bool finalizable) {
  assert(is_aligned(addr, ZMarkPartialArrayMinSize), "Address misaligned");
  ZMarkThreadLocalStacks* const stacks = ZThreadLocalData::stacks(Thread::current());
  ZMarkStripe* const stripe = _stripes.stripe_for_addr(addr);
  const uintptr_t offset = ZAddress::offset(addr) >> ZMarkPartialArrayMinSizeShift;
  const uintptr_t length = size / oopSize;
  const ZMarkStackEntry entry(offset, length, finalizable);

  log_develop_trace(gc, marking)("Array push partial: " PTR_FORMAT " (" SIZE_FORMAT "), stripe: " SIZE_FORMAT,
                                 addr, size, _stripes.stripe_id(stripe));

  stacks->push(&_allocator, &_stripes, stripe, entry, false /* publish */);
}

2.2 标记

标记的流程图大致如下
在这里插入图片描述
share/gc/z/zGlobal.cpp

// Global phase state
extern uint32_t   ZGlobalPhase;
const uint32_t    ZPhaseMark                    = 0;
const uint32_t    ZPhaseMarkCompleted           = 1;
const uint32_t    ZPhaseRelocate                = 2;

share/gc/z/zBarrier.inline.hpp

inline bool ZBarrier::during_relocate() {
  return ZGlobalPhase == ZPhaseRelocate;
}

如上述代码所示,判断转移还是标记,其实就是根据全局变量ZGlobalPhase的值确定的。

share/gc/z/zMark.inline.hpp

template <bool follow, bool finalizable, bool publish>
inline void ZMark::mark_object(uintptr_t addr) {
  assert(ZAddress::is_marked(addr), "Should be marked");
  ZMarkThreadLocalStacks* const stacks = ZThreadLocalData::stacks(Thread::current());
  ZMarkStripe* const stripe = _stripes.stripe_for_addr(addr);
  ZMarkStackEntry entry(addr, follow, finalizable);

  stacks->push(&_allocator, &_stripes, stripe, entry, publish);
}
  • 将对象压入标记栈

share/gc/z/zMark.cpp

void ZMark::work_without_timeout(ZMarkCache* cache, ZMarkStripe* stripe, ZMarkThreadLocalStacks* stacks) {
  ZStatTimer timer(ZSubPhaseConcurrentMark);
  ZMarkNoTimeout no_timeout;

  for (;;) {
    drain_and_flush(stripe, stacks, cache, &no_timeout);

    if (try_steal(stripe, stacks)) {
      // Stole work
      continue;
    }

    if (try_proactive_flush()) {
      // Work available
      continue;
    }

    if (try_terminate()) {
      // Terminate
      break;
    }
  }
}

调用链非常复杂,work_without_timeout -> drain_and_flush -> mark_and_follow,mark_and_follow是实际标记逻辑。

share/gc/z/zMark.cpp

void ZMark::mark_and_follow(ZMarkCache* cache, ZMarkStackEntry entry) {
  // Decode flags
  const bool finalizable = entry.finalizable();
  const bool partial_array = entry.partial_array();

  if (partial_array) {
    // 之前入栈的并行数组对象的处理
    follow_partial_array(entry, finalizable);
    return;
  }

  const uintptr_t addr = entry.object_address();

  if (!try_mark_object(cache, addr, finalizable)) {
    // 已标记对象,直接退出
    return;
  }

  if (is_array(addr)) {
    // 数组对象
    const bool follow = entry.follow();

    if (follow) {
      // 标记数组对象
      follow_array_object(objArrayOop(ZOop::from_address(addr)), finalizable);
    }
  } else {
    // 标记普通对象
    // 进入load barrier代码标记
    follow_object(ZOop::from_address(addr), finalizable);
  }
}

share/gc/z/zMark.cpp

void ZMark::follow_array(uintptr_t addr, size_t size, bool finalizable) {
  // 是否小于4KB
  if (size <= ZMarkPartialArrayMinSize) {
    // 小数组直接标记
    follow_small_array(addr, size, finalizable);
  } else {
    // 大数组分段入栈
    follow_large_array(addr, size, finalizable);
  }
}
  • 标记数组对象,根据数组的size分为大数组和小数组。

2.3 计数

并发标记阶段,除了标记之外,还需要统计活跃对象的个数和活跃对象占用空间,ZGC使用ZLiveMap维护活跃对象信息。
share/gc/z/zLiveMap.hpp

class ZLiveMap {
  friend class ZLiveMapTest;

private:
  // 分64段,便于多线程处理
  static const size_t nsegments = 64;

  // 页面的纪元,用于分辨页面是本轮GC启动前分配的还是启动后分配的
  volatile uint32_t _seqnum;
  // 活跃对象个数
  volatile uint32_t _live_objects;
  // 活跃对象size
  volatile size_t   _live_bytes;
  // 记录分段的存活状态
  BitMap::bm_word_t _segment_live_bits;
  // 记录分段的回收状态
  BitMap::bm_word_t _segment_claim_bits;
  // 页面标记位图,ZLiveMap最重要的字段,虽然1bit就可以标记一个对象,但为了区分强引用和弱引用,使用2bit标记一个对象
  ZBitMap           _bitmap;
  size_t            _segment_shift;
}

_bitmap位图的大小由Page type决定:
share/gc/z/zPage.inline.hpp

ZPage::ZPage(uint8_t type, const ZVirtualMemory& vmem, const ZPhysicalMemory& pmem) :
    _type(type),
    _numa_id((uint8_t)-1),
    _seqnum(0),
    _virtual(vmem),
    _top(start()),
    // 创建ZLiveMap
    _livemap(object_max_count()),
    _last_used(0),
    _physical(pmem),
    _node() {
  assert_initialized();
}

inline uint32_t ZPage::object_max_count() const {
  switch (type()) {
  case ZPageTypeLarge:
    // large page永远只包含一个对象,所以返回1
    return 1;

  default:
    // small和medium page按最小对象size计算
    // small page对象按8字节对齐,2MB/8B = 262144
    // medium page对象最小256KB,32MB/256KB = 128
    return (uint32_t)(size() >> object_alignment_shift());
  }
}

share/gc/z/zLiveMap.cpp

static size_t bitmap_size(uint32_t size, size_t nsegments) {
  // object_max_count()计算的size和64取最大值后乘2
  // 即为bitmap的size
  return MAX2<size_t>(size, nsegments) * 2;
}

按如下方式区分强引用和弱引用
share/gc/z/zPage.inline.hpp

// 对象是否被标记, index
inline bool ZPage::is_object_marked(uintptr_t addr) const {
  const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;
  return _livemap.get(index);
}

// 对象是否被强引用标记, index+1
inline bool ZPage::is_object_strongly_marked(uintptr_t addr) const {
  const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;
  return _livemap.get(index + 1);
}

3. 总结

并发标记阶段主要是根据初始标记从GC roots找到的对象,使用深度优先遍历对象的成员变量进行标记,并记录活跃对象位图。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值