上文介绍了 G1 遍历 GC root 的源码,已经知道 G1 将与 GC root 对象直接关联的对象加入到任务队列中,复制对象也叫 evacuate object 主要是任务就是处理任务队列,直至队列为空为止。

任务队列

任务队列在垃圾收集过程中扮演着非常重要的角色,首先看看任务队列如何被初始化和内部结构。

从下面的代码可以看出队列集合的大小和 GC 线程大小一致。

G1CollectedHeap::G1CollectedHeap() :{
  uint n_queues = ParallelGCThreads;
  _task_queues = new G1ScannerTasksQueueSet(n_queues);
  for (uint i = 0; i < n_queues; i++) {
    G1ScannerTasksQueue* q = new G1ScannerTasksQueue();
    _task_queues->register_queue(i, q);
  }
}

OverflowTaskQueue 分成两部分:

taskqueue_t 是一个队列,队列大小由 TASKQUEUE_SIZE 指定,底层 _elems 是一个数组。 它是一个双端出队队列,pop_local 是队列持有者线程获取任务,pop_global 是其他线程获取任务,前者是从入队的位置出队。

template<class E, MemTag MT, unsigned int N = TASKQUEUE_SIZE>
class OverflowTaskQueue: public GenericTaskQueue<E, MT, N>
{
  typedef Stack<E, MT>               overflow_t;
  typedef GenericTaskQueue<E, MT, N> taskqueue_t;
  // Element array.
  E* _elems;
  inline bool pop_local(E& t, uint threshold = 0);
  PopResult pop_global(E& t);
}

overflow_t 是一个栈,其内部是一个链表连接的数组,大致结构如上图。

G1ParScanThreadState 初始化的实际被绑定到线程上。

G1ParScanThreadState::G1ParScanThreadState(...):
  _task_queue(g1h->task_queue(worker_id)),
{ }

本地任务处理

pss->trim_queue() 负责处理线程本地任务队列。

class G1EvacuateRegionsBaseTask : public WorkerTask {
    evacuate_live_objects(.....){
    G1ParEvacuateFollowersClosure cl(........);
    cl.do_void();
    } }

class G1ParEvacuateFollowersClosure : public VoidClosure {
    void do_void() {
    G1ParScanThreadState* const pss = par_scan_state();
    pss->trim_queue();
    do {
      pss->steal_and_trim_queue(queues());
    } while (!offer_termination());
  }
}

trim_queue_to_threshold 首先将溢出栈里面的任务加入到队列中,避免溢出栈过长,增加开销,而且队列中的任务是可以并行处理的。加入失败时直接处理任务。

pop_local 方法从队列中取出任务直到队列为空。

//->pss->trim_queue() -> trim_queue_to_threshold(0)
void G1ParScanThreadState::trim_queue_to_threshold(uint threshold) {
  ScannerTask task;
  do {
    while (_task_queue->pop_overflow(task)) {
      if (!_task_queue->try_push_to_taskqueue(task)) {
        dispatch_task(task);
      }
    }
    while (_task_queue->pop_local(task, threshold)) {
      dispatch_task(task);
    }
  } while (!_task_queue->overflow_empty());
}

G1ParScanThreadState::do_oop_evac 处理逻辑与 G1ParCopyClosure 类似不在赘述。

//-> G1ParScanThreadState::dispatch_task 
void G1ParScanThreadState::do_oop_evac(T* p) {
  oop obj = RawAccess<IS_NOT_NULL>::oop_load(p);

  const G1HeapRegionAttr region_attr = _g1h->region_attr(obj);
  if (!region_attr.is_in_cset()) { return; }

  markWord m = obj->mark();
  if (m.is_forwarded()) {
    obj = m.forwardee();
  } else {
    obj = do_copy_to_survivor_space(region_attr, obj, m);
  }
  RawAccess<IS_NOT_NULL>::oop_store(p, obj);

  write_ref_field_post(p, obj);
}

全局任务处理

steal_and_trim_queue 负责从其他队列窃取任务并且执行,当本地任务不为空时,先清空本地任务。

_n 是队列的数量,num_retries 表示尝试次数是队列数量的两倍。

void G1ParScanThreadState::steal_and_trim_queue(G1ScannerTasksQueueSet* task_queues) {
  ScannerTask stolen_task;
  while (task_queues->steal(_worker_id, stolen_task)) {
    dispatch_task(stolen_task);
    // Processing stolen task may have added tasks to our queue.
    trim_queue(); //处理其他任务队列的过程中,会将后续任务加入到本地队列中。
  }
}

bool GenericTaskQueueSet<T, MT>::steal(uint queue_num, E& t) {
  uint const num_retries = 2 * _n;
  for (uint i = 0; i < num_retries; i++) {
    PopResult sr = steal_best_of_2(queue_num, t);
    if (sr == PopResult::Success) {
      return true;
    } else if (sr == PopResult::Contended) {
    } else {
    }
  }
  return false;
}

steal_best_of_2 复杂随机选取两个队列,从 size 较大队列获取获取任务。

其中 k1代表的队列要么随机获取,要么是获取的队列,k2 代表的队列随机获取。

steal_best_of_2(uint queue_num, E& t){
  uint k1 = queue_num; 

  if (local_queue->is_last_stolen_queue_id_valid()) {
    k1 = local_queue->last_stolen_queue_id();
    assert(k1 != queue_num, "Should not be the same");
  } else {
    while (k1 == queue_num) {
    k1 = local_queue->next_random_queue_id() % _n;
   }
  }

  uint k2 = queue_num;
  while (k2 == queue_num || k2 == k1) {
    k2 = local_queue->next_random_queue_id() % _n;
  }

  // Sample both and try the larger.
  uint sz1 = queue(k1)->size();
  uint sz2 = queue(k2)->size();

  uint sel_k = 0;
  PopResult suc = PopResult::Empty;
  if (sz2 > sz1) {
    sel_k = k2;
    suc = queue(k2)->pop_global(t);
  } else if (sz1 > 0) {
    sel_k = k1;
    suc = queue(k1)->pop_global(t);
  }
  if (suc == PopResult::Success) { local_queue->set_last_stolen_queue_id(sel_k);
  } else { local_queue->invalidate_last_stolen_queue_id(); }
  return suc;
}

直到所有队列中的任务都处理完成,那么对象的遍历也就完成了。

复制失败处理

forward_to_atomic 让对象自转发,当前线程获取拥有权。

_evac_failure_regions->record 记录失败的 region, 并统计失败的次数。

_g1h->mark_evac_failure_object 在 bitmap 中标记,便于后面处理。

` _preserved_marks->push_if_necessary 表示如果当前对象处于锁状态或者 hash 值已经计算则需要将此时的 markword` 记录下来。后面需要回复。

old->oop_iterate_backwards 遍历对象属性。

oop G1ParScanThreadState::handle_evacuation_failure_par(oop old, markWord m, size_t word_sz, bool cause_pinned) {

  oop forward_ptr = old->forward_to_atomic(old, m, memory_order_relaxed);
  if (forward_ptr == nullptr) {
    // Forward-to-self succeeded. We are the "owner" of the object.
    G1HeapRegion* r = _g1h->heap_region_containing(old);

    if (_evac_failure_regions->record(_worker_id, r->hrm_index(), cause_pinned)) {
      G1HeapRegionPrinter::evac_failure(r);
    }

    // Mark the failing object in the marking bitmap and later use the bitmap to handle
    // evacuation failure recovery.
    _g1h->mark_evac_failure_object(_worker_id, old, word_sz);

    _preserved_marks->push_if_necessary(old, m);

    ContinuationGCSupport::transform_stack_chunk(old);

    _evacuation_failed_info.register_copy_failure(word_sz);

    G1SkipCardEnqueueSetter x(&_scanner, false /* skip_card_enqueue */);
    old->oop_iterate_backwards(&_scanner);

    return old;
  }else {} //omit
}

总结

本文介绍了对象复制的逻辑,着重说明了线程本地队列和全局队列任务的处理,最后简单描述了对于对象复制失败的处理。