【jvm学习笔记五】G1-YGC分析

在内存分配的时候,如果剩余空间不能满足分配的对象就会触发YGC。G1每次都会收集所有的新生代的分区,但新生代分区的数目每次可能是不一样的,这是因为G1会根据时间预测模型来调整新生代分区数目。

YGC算法步骤

YGC算法主要分并行和其他部分,主要步骤如下:

  1. 收集之前STW;
  2. 选择要收集的Cset,即整个新生代分区
  3. 进入并行处理:
    3.1 根扫描并处理:根直接引用的对象放到s区,然后把对象的field入栈等待后续复制处理;
    3.2 处理老年代到新生代的引用:更新RSet,从RSet出发,把RSet所在卡表对应的分区内存块所有对象认为是根,并将根引用的对象复制到s区,其field入栈等待后续复制处理;
    3.3 JIT代码扫描
    3.4 递归处理栈中的对象
  4. 其他处理:
    4.1 JIT代码位置更新
    4.2 引用处理
    4.3 字符串去重优化回收
    4.4 清除卡表
    4.5 JIT代码回收
    4.6 Redirty、释放CSet、尝试大对象回收等
    4.7 可能启动并发标记:内存超过阈值,则启动

并行任务处理

image.png

并行任务是图中的第二步

  1. GC worker start (g1CollectedHeap)
void work(uint worker_id) {
    if (worker_id >= _n_workers) return;  // no work needed this round

    double start_time_ms = os::elapsedTime() * 1000.0;
    _g1h->g1_policy()->phase_times()->record_gc_worker_start_time(worker_id, start_time_ms);
   ...
}
  1. Ext Root Scanning(g1CollectedHeap)
void
G1CollectedHeap::
g1_process_roots(OopClosure* scan_non_heap_roots,
                 OopClosure* scan_non_heap_weak_roots,
                 OopsInHeapRegionClosure* scan_rs,
                 CLDClosure* scan_strong_clds,
                 CLDClosure* scan_weak_clds,
                 CodeBlobClosure* scan_strong_code,
                 uint worker_i) {

  // First scan the shared roots.
  double ext_roots_start = os::elapsedTime();
  double closure_app_time_sec = 0.0;

  bool during_im = _g1h->g1_policy()->during_initial_mark_pause();
  bool trace_metadata = during_im && ClassUnloadingWithConcurrentMark;

  BufferingOopClosure buf_scan_non_heap_roots(scan_non_heap_roots);
  BufferingOopClosure buf_scan_non_heap_weak_roots(scan_non_heap_weak_roots);

  process_roots(false, // no scoping; this is parallel code
                SharedHeap::SO_None,
                &buf_scan_non_heap_roots,
                &buf_scan_non_heap_weak_roots,
                scan_strong_clds,
                // Unloading Initial Marks handle the weak CLDs separately.
                (trace_metadata ? NULL : scan_weak_clds),
                scan_strong_code);

  // Now the CM ref_processor roots.
  if (!_process_strong_tasks->is_task_claimed(G1H_PS_refProcessor_oops_do)) {
    // We need to treat the discovered reference lists of the
    // concurrent mark ref processor as roots and keep entries
    // (which are added by the marking threads) on them live
    // until they can be processed at the end of marking.
    ref_processor_cm()->weak_oops_do(&buf_scan_non_heap_roots);
  }

  if (trace_metadata) {
    // Barrier to make sure all workers passed
    // the strong CLD and strong nmethods phases.
    active_strong_roots_scope()->wait_until_all_workers_done_with_threads(n_par_threads());

    // Now take the complement of the strong CLDs.
    ClassLoaderDataGraph::roots_cld_do(NULL, scan_weak_clds);
  }

  // Finish up any enqueued closure apps (attributed as object copy time).
  buf_scan_non_heap_roots.done();
  buf_scan_non_heap_weak_roots.done();

  double obj_copy_time_sec = buf_scan_non_heap_roots.closure_app_seconds()
      + buf_scan_non_heap_weak_roots.closure_app_seconds();

  g1_policy()->phase_times()->record_obj_copy_time(worker_i, obj_copy_time_sec * 1000.0);

  double ext_root_time_ms =
    ((os::elapsedTime() - ext_roots_start) - obj_copy_time_sec) * 1000.0;

  g1_policy()->phase_times()->record_ext_root_scan_time(worker_i, ext_root_time_ms);

  // During conc marking we have to filter the per-thread SATB buffers
  // to make sure we remove any oops into the CSet (which will show up
  // as implicitly live).
  double satb_filtering_ms = 0.0;
  if (!_process_strong_tasks->is_task_claimed(G1H_PS_filter_satb_buffers)) {
    if (mark_in_progress()) {
      double satb_filter_start = os::elapsedTime();

      JavaThread::satb_mark_queue_set().filter_thread_buffers();

      satb_filtering_ms = (os::elapsedTime() - satb_filter_start) * 1000.0;
    }
  }
  g1_policy()->phase_times()->record_satb_filtering_time(worker_i, satb_filtering_ms);

  // Now scan the complement of the collection set.
  G1CodeBlobClosure scavenge_cs_nmethods(scan_non_heap_weak_roots);

  g1_rem_set()->oops_into_collection_set_do(scan_rs, &scavenge_cs_nmethods, worker_i);

  _process_strong_tasks->all_tasks_completed();
}

在process_root方法中,会通过Threads::possibly_parallel_oops_do方法遍历所有的java线程和VMThread线程进行栈处理

void JavaThread::oops_do(OopClosure* f, CLDClosure* cld_f, CodeBlobClosure* cf) {
  
  // 处理JNI本地栈、JVM内部本地方法栈
  Thread::oops_do(f, cld_f, cf);

  if (has_last_Java_frame()) {
    // Record JavaThread to GC thread
    RememberProcessedThread rpt(this);

    // 处理用于实现安全功能的类
    if (_privileged_stack_top != NULL) {
      _privileged_stack_top->oops_do(f);
    }

    // traverse the registered growable array
    if (_array_for_gc != NULL) {
      for (int index = 0; index < _array_for_gc->length(); index++) {
        f->do_oop(_array_for_gc->adr_at(index));
      }
    }

    // 处理monitor块
    for (MonitorChunk* chunk = monitor_chunks(); chunk != NULL; chunk = chunk->next()) {
      chunk->oops_do(f);
    }

    // 遍历栈
    for(StackFrameStream fst(this); !fst.is_done(); fst.next()) {
      fst.current()->oops_do(f, cld_f, cf, fst.register_map());
    }
  }

  // callee_target is never live across a gc point so NULL it here should
  // it still contain a methdOop.

  set_callee_target(NULL);

  assert(vframe_array_head() == NULL, "deopt in progress at a safepoint!");
  // If we have deferred set_locals there might be oops waiting to be
  // written
  GrowableArray<jvmtiDeferredLocalVariableSet*>* list = deferred_locals();
  if (list != NULL) {
    for (int i = 0; i < list->length(); i++) {
      list->at(i)->oops_do(f);
    }
  }

  // 遍历这些实例对象,它们可能引用了堆对象
  f->do_oop((oop*) &_threadObj);
  f->do_oop((oop*) &_vm_result);
  f->do_oop((oop*) &_exception_oop);
  f->do_oop((oop*) &_pending_async_exception);

  if (jvmti_thread_state() != NULL) {
    jvmti_thread_state()->oops_do(f);
  }
}
  1. update RSet (g1RemSet)
void G1RemSet::updateRS(DirtyCardQueue* into_cset_dcq, uint worker_i) {
  double start = os::elapsedTime();
  // Apply the given closure to all remaining log entries.
  //使用closure处理DCQ队列
  RefineRecordRefsIntoCSCardTableEntryClosure into_cset_update_rs_cl(_g1, into_cset_dcq);
  // 遍历处理dirty_card
  _g1->iterate_dirty_card_closure(&into_cset_update_rs_cl, into_cset_dcq, false, worker_i);

  // Now there should be no dirty cards.
  if (G1RSLogCheckCardTable) {
    CountNonCleanMemRegionClosure cl(_g1);
    _ct_bs->mod_card_iterate(&cl);
    // XXX This isn't true any more: keeping cards of young regions
    // marked dirty broke it.  Need some reasonable fix.
    guarantee(cl.n() == 0, "Card table should be clean.");
  }

  _g1p->phase_times()->record_update_rs_time(worker_i, (os::elapsedTime() - start) * 1000.0);
}

void G1CollectedHeap::iterate_dirty_card_closure(CardTableEntryClosure* cl,
                                                 DirtyCardQueue* into_cset_dcq,
                                                 bool concurrent,
                                                 uint worker_i) {
  // Clean cards in the hot card cache
  //处理热表
  G1HotCardCache* hot_card_cache = _cg1r->hot_card_cache();
  hot_card_cache->drain(worker_i, g1_rem_set(), into_cset_dcq);
  //处理DCQS中剩下的DCQ
  DirtyCardQueueSet& dcqs = JavaThread::dirty_card_queue_set();
  int n_completed_buffers = 0;
  while (dcqs.apply_closure_to_completed_buffer(cl, worker_i, 0, true)) {
    n_completed_buffers++;
  }
  g1_policy()->phase_times()->record_update_rs_processed_buffers(worker_i, n_completed_buffers);
  dcqs.clear_n_completed_buffers();
  assert(!dcqs.completed_buffers_exist_dirty(), "Completed buffers exist!");
}
  1. Scan Rs (G1RemSet)
void G1RemSet::scanRS(OopsInHeapRegionClosure* oc,
                      CodeBlobClosure* code_root_cl,
                      uint worker_i) {
  double rs_time_start = os::elapsedTime();
  //每个线程处理部分分区
  HeapRegion *startRegion = _g1->start_cset_region_for_worker(worker_i);

  ScanRSClosure scanRScl(oc, code_root_cl, worker_i);
  //第一次扫描,处理一般对象
  _g1->collection_set_iterate_from(startRegion, &scanRScl);
  scanRScl.set_try_claimed();
  //第二次扫描,处理代码对象
  _g1->collection_set_iterate_from(startRegion, &scanRScl);

  double scan_rs_time_sec = (os::elapsedTime() - rs_time_start)
                            - scanRScl.strong_code_root_scan_time_sec();

  assert(_cards_scanned != NULL, "invariant");
  _cards_scanned[worker_i] = scanRScl.cards_done();

  _g1p->phase_times()->record_scan_rs_time(worker_i, scan_rs_time_sec * 1000.0);
  _g1p->phase_times()->record_strong_code_root_scan_time(worker_i,
                                                         scanRScl.strong_code_root_scan_time_sec() * 1000.0);
}
  1. Code Root Scaning
void scan_strong_code_roots(HeapRegion* r) {
    double scan_start = os::elapsedTime();
    r->strong_code_roots_do(_code_root_cl);
    _strong_code_root_scan_time_sec += (os::elapsedTime() - scan_start);
  }
  1. Object Copy (g1CollectedHeap)
    在遍历java栈时,会将对象复制到s区
void G1ParCopyClosure<barrier, do_mark_object>::do_oop_work(T* p) {
  T heap_oop = oopDesc::load_heap_oop(p);

  if (oopDesc::is_null(heap_oop)) {
    return;
  }

  oop obj = oopDesc::decode_heap_oop_not_null(heap_oop);

  G1CollectedHeap::in_cset_state_t state = _g1->in_cset_state(obj);

  if (state == G1CollectedHeap::InCSet) {
    oop forwardee;
    //对象是否已复制完成
    if (obj->is_forwarded()) {
      forwardee = obj->forwardee();
    } else {
      //复制对象到s区
      forwardee = _par_scan_state->copy_to_survivor_space(obj);
    }
    assert(forwardee != NULL, "forwardee should not be NULL");
    oopDesc::encode_store_heap_oop(p, forwardee);
    if (do_mark_object != G1MarkNone && forwardee != obj) {
      // 如果对象成功复制,把对象的新地址设置到老对象的对象头.
      mark_forwarded_object(obj, forwardee);
    }

    if (barrier == G1BarrierKlass) {
      do_klass_barrier(p, forwardee);
    }
  } else {
    //对不在CSet中的对象,先标记为活的,到时作为跟对象
    if (state == G1CollectedHeap::IsHumongous) {
      _g1->set_humongous_is_live(obj);
    }
    // The object is not in collection set. If we're a root scanning
    // closure during an initial mark pause then attempt to mark the object.
    if (do_mark_object == G1MarkFromRoot) {
      mark_object(obj);
    }
  }
  //如果是eval失败,则将对象记录在一个队列中
  if (barrier == G1BarrierEvac) {
    _par_scan_state->update_rs(_from, p, _worker_id);
  }
}

copy对象到s区的实现

oop G1ParScanThreadState::copy_to_survivor_space(oop const old) {
  size_t word_sz = old->size();
  HeapRegion* from_region = _g1h->heap_region_containing_raw(old);
  // +1 to make the -1 indexes valid...
  int       young_index = from_region->young_index_in_cset()+1;
  
  G1CollectorPolicy* g1p = _g1h->g1_policy();
  markOop m = old->mark();
  //根据age和s区是否放的下来判断对象是copy到s区还是old区
  int age = m->has_displaced_mark_helper() ? m->displaced_mark_helper()->age()
                                           : m->age();
  GCAllocPurpose alloc_purpose = g1p->evacuation_destination(from_region, age,
                                                             word_sz);
  AllocationContext_t context = from_region->allocation_context();
  //使用plab方法在plab分配(plab和tlab一样是为了避免并发,只是plab是分配在s和old区)
  HeapWord* obj_ptr = _g1_par_allocator->allocate(alloc_purpose, word_sz, context);

  if (_g1h->evacuation_should_fail()) {
    if (obj_ptr != NULL) {
      _g1_par_allocator->undo_allocation(alloc_purpose, obj_ptr, word_sz, context);
      obj_ptr = NULL;
    }
  }

  if (obj_ptr == NULL) {
    // plab分配失败,则判断是否需要再分配plab,大小由youngPLABSzie和OldPLABSize决定,当然还有浪费的比例ParallelGCBufferWasterPct参数
    return _g1h->handle_evacuation_failure_par(this, old);
  }

  oop obj = oop(obj_ptr);

  // We're going to allocate linearly, so might as well prefetch ahead.
  Prefetch::write(obj_ptr, PrefetchCopyIntervalInBytes);

  oop forward_ptr = old->forward_to_atomic(obj);
  if (forward_ptr == NULL) {
    //如果对象头没有指针,说明是第一次复制,增加引用关系
    Copy::aligned_disjoint_words((HeapWord*) old, obj_ptr, word_sz);
    HeapRegion* to_region = _g1h->heap_region_containing_raw(obj_ptr);
    alloc_purpose = to_region->is_young() ? GCAllocForSurvived : GCAllocForTenured;

    if (g1p->track_object_age(alloc_purpose)) {
      if (m->has_displaced_mark_helper()) {
         //更新age信息和对象头
        obj->set_mark(m);
        obj->incr_age();
      } else {
        m = m->incr_age();
        obj->set_mark(m);
      }
      age_table()->add(obj, word_sz);
    } else {
      obj->set_mark(m);
    }
    //字符串去重
    if (G1StringDedup::is_enabled()) {
      G1StringDedup::enqueue_from_evacuation(from_region->is_young(),
                                             to_region->is_young(),
                                             queue_num(),
                                             obj);
    }

    size_t* surv_young_words = surviving_young_words();
    surv_young_words[young_index] += word_sz;
    //处理数组对象,放入队列后续处理,防止数组过大深度遍历时导致处理队列溢出
    if (obj->is_objArray() && arrayOop(obj)->length() >= ParGCArrayScanChunk) {
      arrayOop(obj)->set_length(0);
      oop* old_p = set_partial_array_mask(old);
      push_on_queue(old_p);
    } else {
      //
      obj->oop_iterate_backwards(&_scanner);
    }
  } else {
    _g1_par_allocator->undo_allocation(alloc_purpose, obj_ptr, word_sz, context);
    obj = forward_ptr;
  }
  return obj;
}

处理每个对象的field

inline void G1CMOopClosure::do_oop_nv(T* p) {
  oop obj = oopDesc::load_decode_heap_oop(p);
  if (_cm->verbose_high()) {
    gclog_or_tty->print_cr("[%u] we're looking at location "
                           "*"PTR_FORMAT" = "PTR_FORMAT,
                           _task->worker_id(), p2i(p), p2i((void*) obj));
  }
    //遍历对象的每个field进行处理
  _task->deal_with_reference(obj);
}

主要部分就到这里了,最后再。。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容