天天看點

記憶體代管理器DefNewGeneration對垃圾對象的回收(Minor Gc)

    記憶體堆管理器GenCollectedHeap在執行Gc回收垃圾對象的時候,無論是它自己還是垃圾回收政策MarkSweepPolicy都沒有具體去規定一個垃圾對象應該如何被回收,而隻是在整體上決策這次的Gc應該回收那些記憶體代中的垃圾對象,至于這些記憶體代管理器是如何做的,GenCollectedHeap并不關心. 從前文分析記憶體堆管理器GenCollectedHeap執行Gc政策排程記憶體代管理器去回收該記憶體代中垃圾對象的過程, 不難發現,主要涉及到記憶體代管理器的幾個核心的方法實作:

bool full_collects_younger_generations();	//目前記憶體代是否支援回收比其年青的所有記憶體代的垃圾
bool should_collect(bool full, size_t word_size, bool is_tlab);	//目前記憶體代是否應該進行垃圾回收
void collect(bool full, bool clear_all_soft_refs, size_t word_size, bool is_tlab) = 0;	//對目前記憶體代進行垃圾回收
           

    總體上來說,記憶體代管理器DefNewGeneration回收垃圾對象的基本思路就是,首先掃描所有的根對象集T,并将它們複制到新的存儲空間(一般是年青代的To記憶體區和舊生代),然後分析掃描這些根對象集T的所有所有引用對象集T1,并也把這些引用對象集T1複制到新的存儲空間;再分析掃描引用對象集T1有所有引用對象集T2,.....;就這樣一直疊代下去一直到引用對象集Tn為空為止.

記憶體代管理器DefNewGeneration對垃圾對象的回收(Minor Gc)

    由于DefNewGeneration隻能用作新生代的記憶體管理器,是以它是不可能支援其它年青代垃圾對象回收的:

//目前記憶體代是否支援回收比其年青的所有記憶體代的垃圾
virtual bool full_collects_younger_generations() const { return false; }

/**
  * 目前記憶體代是否應該進行垃圾回收
 */
virtual bool should_collect(bool   full,
                              size_t word_size,
                              bool   is_tlab) {
    return (full || should_allocate(word_size, is_tlab));
}
           

    對于多數記憶體代管理器而言,它們在處理垃圾對象的時候基本上都會涉及到掃描-标記-處理等操作,是以而設計了一個抽象通用的資料結構OopClosure(對象表),專門用來做這個操作:

記憶體代管理器DefNewGeneration對垃圾對象的回收(Minor Gc)

  在記憶體代管理器DefNewGeneration回收垃圾對象的過程中,使用FastScanClosure處理所有的根對象,FastEvacuateFollowersClosure處理所有的引用對象,而所有的軟/弱引用對象則使用FastKeepAliveClosure來處理:

/**
 * 執行本記憶體代的垃圾對象回收
 */
void DefNewGeneration::collect(bool   full,
                               bool   clear_all_soft_refs,
                               size_t size,
                               bool   is_tlab) {
  assert(full || size > 0, "otherwise we don't want to collect");

  GenCollectedHeap* gch = GenCollectedHeap::heap();

  //下一個記憶體代管理器(主要用于active對象更新或轉存儲)
  _next_gen = gch->next_gen(this);
  //目前記憶體代管理器隻用于年青代
  assert(_next_gen != NULL, "This must be the youngest gen, and not the only gen");

  //檢查回收目前記憶體代的垃圾對象是否安全,若不安全則放棄回收該記憶體代,并通知記憶體堆管理器關閉目前的增量式垃圾回收方式
  if (!collection_attempt_is_safe()) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print(" :: Collection attempt not safe :: ");
    }

    //告訴記憶體堆管理器不要再考慮增量式Gc(Minor Gc),因為一定會失敗
    gch->set_incremental_collection_failed(); // Slight lie: we did not even attempt one
    return;
  }

  assert(to()->is_empty(), "Else not collection_attempt_is_safe");

  init_assuming_no_promotion_failure();

  TraceTime t1("GC", PrintGC && !PrintGCDetails, true, gclog_or_tty);

  //記錄該Gc之前,記憶體堆的使用量
  size_t gch_prev_used = gch->used();

  SpecializationStats::clear();

  // These can be shared for all code paths
  IsAliveClosure is_alive(this);
  ScanWeakRefClosure scan_weak_ref(this);

  age_table()->clear();
  to()->clear(SpaceDecorator::Mangle);

  gch->rem_set()->prepare_for_younger_refs_iterate(false);

  //标記所有記憶體代目前配置設定對象存儲空間的起始位置
  assert(gch->no_allocs_since_save_marks(0), "save marks have not been newly set.");

  // Not very pretty.
  CollectorPolicy* cp = gch->collector_policy();

  FastScanClosure fsc_with_no_gc_barrier(this, false);
  FastScanClosure fsc_with_gc_barrier(this, true);

  set_promo_failure_scan_stack_closure(&fsc_with_no_gc_barrier);
  //用于分析對象的引用關系并進行回收
  FastEvacuateFollowersClosure evacuate_followers(gch, _level, this,
                                                  &fsc_with_no_gc_barrier,
                                                  &fsc_with_gc_barrier);

  //标記所有記憶體代目前配置設定對象存儲空間的起始位置
  assert(gch->no_allocs_since_save_marks(0), "save marks have not been newly set.");

  //周遊目前記憶體代上的所有根對象,并複制它們到新的存儲空間
  gch->gen_process_strong_roots(_level,
                                true,  // Process younger gens, if any, as strong roots.
                                true,  // activate StrongRootsScope
                                false, // not collecting perm generation.
                                SharedHeap::SO_AllClasses,
                                &fsc_with_no_gc_barrier,
                                true,   // walk *all* scavengable nmethods
                                &fsc_with_gc_barrier);

  //疊代遞歸周遊目前記憶體代上的所有根對象的引用對象,并進行垃圾回收(複制active對象到新的存儲空間)
  evacuate_followers.do_void();

  //清理軟引用對象
  FastKeepAliveClosure keep_alive(this, &scan_weak_ref);
  ReferenceProcessor* rp = ref_processor();
  rp->setup_policy(clear_all_soft_refs);
  rp->process_discovered_references(&is_alive, &keep_alive, &evacuate_followers, NULL);

  if (!promotion_failed()) {	//目前記憶體代(年青代)在本次Gc過程中沒有發生對象更新失敗

    //Eden/From區清零
    eden()->clear(SpaceDecorator::Mangle);
    from()->clear(SpaceDecorator::Mangle);

    if (ZapUnusedHeapArea) {
      // This is now done here because of the piece-meal mangling which
      // can check for valid mangling at intermediate points in the
      // collection(s).  When a minor collection fails to collect
      // sufficient space resizing of the young generation can occur
      // an redistribute the spaces in the young generation.  Mangle
      // here so that unzapped regions don't get distributed to
      // other spaces.
      to()->mangle_unused_area();
    }

    //交換From/To區
    swap_spaces();

    assert(to()->is_empty(), "to space should be empty now");

    //重新計算對象可進入下一個記憶體代的存活時間門檻值
    _tenuring_threshold = age_table()->compute_tenuring_threshold(to()->capacity()/HeapWordSize);

    // A successful scavenge should restart the GC time limit count which is
    // for full GC's.
    AdaptiveSizePolicy* size_policy = gch->gen_policy()->size_policy();
    size_policy->reset_gc_overhead_limit_count();
    if (PrintGC && !PrintGCDetails) {
      gch->print_heap_change(gch_prev_used);
    }

    assert(!gch->incremental_collection_failed(), "Should be clear");

  } else {	//目前記憶體代(年青代)在本次Gc過程中發生了對象更新失敗(年老代沒有足夠的空閑空間來容納從年青代轉存儲過來的active對象)
    assert(_promo_failure_scan_stack.is_empty(), "post condition");

    _promo_failure_scan_stack.clear(true); // Clear cached segments.

    remove_forwarding_pointers();

    if (PrintGCDetails) {
      gclog_or_tty->print(" (promotion failed) ");
    }

    // Add to-space to the list of space to compact
    // when a promotion failure has occurred.  In that
    // case there can be live objects in to-space
    // as a result of a partial evacuation of eden
    // and from-space.
    swap_spaces();   // For uniformity wrt ParNewGeneration.

    //設定From區下一個可壓縮記憶體區為To區,以便在下一次的Full Gc中壓縮調整
    from()->set_next_compaction_space(to());

    gch->set_incremental_collection_failed();

    //通知舊生代發生了對象更新失敗(你的空閑空間不夠)
    _next_gen->promotion_failure_occurred();

    // Reset the PromotionFailureALot counters.
    NOT_PRODUCT(Universe::heap()->reset_promotion_should_fail();)
  }

  // set new iteration safe limit for the survivor spaces
  from()->set_concurrent_iteration_safe_limit(from()->top());
  to()->set_concurrent_iteration_safe_limit(to()->top());
  SpecializationStats::print();

  // We need to use a monotonically non-deccreasing time in ms
  // or we will see time-warp warnings and os::javaTimeMillis()
  // does not guarantee monotonicity.
  jlong now = os::javaTimeNanos() / NANOSECS_PER_MILLISEC;
  update_time_of_last_gc(now);
}
           

在DefNewGeneration正式進行Gc前會先檢測一下本次Minor Gc是否安全,如果不安全則直接放棄本次Gc,檢查政策是:

1.To區空閑
2.下一個記憶體代的可用空間能夠容納目前記憶體代的所有對象(用于對象更新)
           

具體代碼實作:

/**
 * 檢查本記憶體代目前進行垃圾回收是否安全
 */
bool DefNewGeneration::collection_attempt_is_safe() {
  if (!to()->is_empty()) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print(" :: to is not empty :: ");
    }
    return false;
  }
  if (_next_gen == NULL) {
    GenCollectedHeap* gch = GenCollectedHeap::heap();
    _next_gen = gch->next_gen(this);
    assert(_next_gen != NULL,
           "This must be the youngest gen, and not the only gen");
  }
  return _next_gen->promotion_attempt_is_safe(used());
}
           

1.根對象的掃描處理(FastScanClosure)

     記憶體代管理器DefNewGeneration對根對象的處理很簡單,就是優先考察該根對象是否可以進入舊生代,如果可以則更新到舊生代,否則将它們複制到年青代的To區,如果T此時o區沒有足夠的空閑空間則将它們轉存儲到舊生代;如果舊生代沒有足夠的空間則此次Minor Gc就可以終止了.

template <class T> inline void FastScanClosure::do_oop_work(T* p) {
  T heap_oop = oopDesc::load_heap_oop(p);

  // Should we copy the obj?
  if (!oopDesc::is_null(heap_oop)) {	//非空對象

    oop obj = oopDesc::decode_heap_oop_not_null(heap_oop);

    if ((HeapWord*)obj < _boundary) {	//對象的存儲空間在目前被回收的記憶體代中
      //確定對象不在年青代的To區
      assert(!_g->to()->is_in_reserved(obj), "Scanning field twice?");

      //複制對象到新的存儲空間
      oop new_obj = obj->is_forwarded() ? obj->forwardee() : _g->copy_to_survivor_space(obj);

      //更新對象的存儲空間位址映射
      oopDesc::encode_store_heap_oop_not_null(p, new_obj);

      if (_gc_barrier) {
        // Now call parent closure
        do_barrier(p);
      }

    }
  }
}

inline void FastScanClosure::do_oop_nv(oop* p)       { FastScanClosure::do_oop_work(p); }
inline void FastScanClosure::do_oop_nv(narrowOop* p) { FastScanClosure::do_oop_work(p); }

/**
 * 将Eden/From區中的某個對象複制到To區或年老代中(目前記憶體代正在GC)
 */
oop DefNewGeneration::copy_to_survivor_space(oop old) {
  assert(is_in_reserved(old) && !old->is_forwarded(), "shouldn't be scavenging this oop");
  //複制對象大小
  size_t s = old->size();
  oop obj = NULL;

  //如果複制對象的存活時間還未超過設定的門檻值,則優先将其拷貝到To區
  if (old->age() < tenuring_threshold()) {
    obj = (oop) to()->allocate(s);
  }


  if (obj == NULL) {	//To區記憶體不夠存儲該對象或該對象可以更新到下一個記憶體代(舊生代)
    obj = _next_gen->promote(old, s);
    if (obj == NULL) {
      handle_promotion_failure(old);
      return old;
    }
  } else {		//将複制對象拷貝到To區
    // Prefetch beyond obj
    const intx interval = PrefetchCopyIntervalInBytes;
    Prefetch::write(obj, interval);

    //複制舊對象的值到新對象中
    Copy::aligned_disjoint_words((HeapWord*)old, (HeapWord*)obj, s);

    //增加對象的存活時間
    obj->incr_age();
    age_table()->add(obj, s);
  }

  // Done, insert forward pointer to obj in this header
  old->forward_to(obj);

  return obj;
}
           

  所謂的根對象究竟是來自那些地方的,記憶體代管理器GenCollectedHeap會根據此時Gc的上下文環境來決定,總的來說就是來自三個大的地方:

1).正常的根對象
2).比待回收的記憶體代Older的記憶體代上配置設定的所有對象
3).比待回收的記憶體代Younger的記憶體代上配置設定的所有對象
           

2.引用對象的掃描處理(FastEvacuateFollowersClosure)

    由于對象之間的引用關系一般是一個有向圖結構,而處理圖結構最常用的方式就是疊代式的廣度優先搜尋或升讀優先搜尋.無論哪種方式, 可能都需要使用臨時的存儲空間來儲存下一次疊代的任務(也就是本次疊代的輸出),這種臨時存儲空間一般是棧或者隊列的資料結構.對于記憶體代管理器DefNewGeneration而言,它采用廣度優先的方式來處理所有的引用對象.所使用的臨時的存儲空間則看起來極為巧妙:在處理根對象之前,先标記儲存年青代Eden區和舊生代配置設定記憶體空間的起始位置,然後處理所有的根對象;根對象處理完之後,年青代Eden區和舊生代配置設定記憶體空間的起始位置可能已經都變化了,而變化的這一部分就是需要處理的引用對象,之後的情況一次類推.不難看出,這種處理方式本質上就是隊列的思想.

DefNewGeneration::EvacuateFollowersClosure::
EvacuateFollowersClosure(GenCollectedHeap* gch, int level,
                         ScanClosure* cur, ScanClosure* older) :
  _gch(gch), _level(level),
  _scan_cur_or_nonheap(cur), _scan_older(older)
{}

void DefNewGeneration::EvacuateFollowersClosure::do_void() {
  do {
    _gch->oop_since_save_marks_iterate(_level, _scan_cur_or_nonheap,
                                       _scan_older);
  } while (!_gch->no_allocs_since_save_marks(_level));
}

void GenCollectedHeap::oop_since_save_marks_iterate(int level,          \
                             OopClosureType* cur,                       \
                             OopClosureType* older) {                   \
  _gens[level]->oop_since_save_marks_iterate##nv_suffix(cur);           \
  for (int i = level+1; i < n_gens(); i++) {                            \
    _gens[i]->oop_since_save_marks_iterate##nv_suffix(older);           \
  }                                                                     \
  perm_gen()->oop_since_save_marks_iterate##nv_suffix(older);           \
}

/**
 * 記憶體堆中的個記憶體堆是否都沒有發生新的對象記憶體配置設定事件,同時标記個記憶體代及其各記憶體區目前配置設定對象存儲空間的起始位置
 */
bool GenCollectedHeap::no_allocs_since_save_marks(int level) {
  for (int i = level; i < _n_gens; i++) {
    if (!_gens[i]->no_allocs_since_save_marks()) return false;
  }
  return perm_gen()->no_allocs_since_save_marks();
}
           

以預設的年青代記憶體代DefNewGeneration為例:

void DefNewGeneration::                                         \
oop_since_save_marks_iterate##nv_suffix(OopClosureType* cl) {   \
  cl->set_generation(this);                                     \
  eden()->oop_since_save_marks_iterate##nv_suffix(cl);          \
  to()->oop_since_save_marks_iterate##nv_suffix(cl);            \
  from()->oop_since_save_marks_iterate##nv_suffix(cl);          \
  cl->reset_generation();                                       \
  save_marks();                                                 \
}

void ContiguousSpace::                                                    \
oop_since_save_marks_iterate##nv_suffix(OopClosureType* blk) {            \
  HeapWord* t;                                                            \
  HeapWord* p = saved_mark_word();  //上一次标記的配置設定位置                \
  assert(p != NULL, "expected saved mark");                               \
                                                                          \
  const intx interval = PrefetchScanIntervalInBytes;                      \
  do {                                                                    \
    t = top();    //目前的配置設定位置                                        \
    while (p < t) {                                                       \
      Prefetch::write(p, interval);                                       \
      debug_only(HeapWord* prev = p);                                     \
      oop m = oop(p);                                                     \
      p += m->oop_iterate(blk);                                           \
    }                                                                     \
  } while (t < top());                                                    \
                                                                          \
  set_saved_mark_word(p);                                                 \
}
           

3.軟/弱引用對象的掃描處理(FastKeepAliveClosure)

    記憶體代管理器DefNewGeneration對軟/弱引用對象的處理主要還是依賴于JVM配置的軟/弱引用對象處理政策,目前的實作的引用對象處理政策主要有三種:

1.AlwaysClearPolicy
2.LRUMaxHeapPolicy
3.LRUCurrentHeapPolicy
           

當然,這三種實作政策本文不會做任何的介紹,畢竟這一部分内容跟jdk還扯上了關系,還是蠻複雜的.當節點保留那些引用對象之後,其處理過程和根對象類似.

    記憶體代管理器在Gc完成之後一般會調整本記憶體代中各記憶體區大小,要麼擴充記憶體代記憶體容量,要麼縮小記憶體代記憶體容量.DefNewGeneration會根據年老代目前容量/NewRatio/目前非守護線程數量/NewSizeThreadIncrease來确定年青代的容量,大體原則是:

擴充年青代的實體空間: From/To區完全空閑
縮小年青代的實體空間: Eden/From/To區完全空閑
           

其具體實作是:

void DefNewGeneration::compute_new_size() {
  // This is called after a gc that includes the following generation
  // (which is required to exist.)  So from-space will normally be empty.
  // Note that we check both spaces, since if scavenge failed they revert roles.
  // If not we bail out (otherwise we would have to relocate the objects)
  if (!from()->is_empty() || !to()->is_empty()) {
    return;
  }

  int next_level = level() + 1;
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  assert(next_level < gch->_n_gens,
         "DefNewGeneration cannot be an oldest gen");
  //老年代的記憶體容量
  Generation* next_gen = gch->_gens[next_level];
  size_t old_size = next_gen->capacity();

  //年青代的目前容量/最小容量/最大容量
  size_t new_size_before = _virtual_space.committed_size();
  size_t min_new_size = spec()->init_size();
  size_t max_new_size = reserved().byte_size();
  assert(min_new_size <= new_size_before && new_size_before <= max_new_size, "just checking");

  // All space sizes must be multiples of Generation::GenGrain.
  size_t alignment = Generation::GenGrain;

  //根據參數NewRatio、NewSizeThreadIncrease來确定年青代的新容量
  size_t desired_new_size = old_size/NewRatio;
  int threads_count = Threads::number_of_non_daemon_threads();
  size_t thread_increase_size = threads_count * NewSizeThreadIncrease;
  desired_new_size = align_size_up(desired_new_size + thread_increase_size, alignment);

  // Adjust new generation size
  desired_new_size = MAX2(MIN2(desired_new_size, max_new_size), min_new_size);
  assert(desired_new_size <= max_new_size, "just checking");

  bool changed = false;

  //擴張年青代的實體空間
  if (desired_new_size > new_size_before) {
    size_t change = desired_new_size - new_size_before;
    assert(change % alignment == 0, "just checking");
    if (expand(change)) {
       changed = true;
    }
    // If the heap failed to expand to the desired size,
    // "changed" will be false.  If the expansion failed
    // (and at this point it was expected to succeed),
    // ignore the failure (leaving "changed" as false).
  }

  //如果目前Eden區完全空閑,則縮小年青代的實體空間
  if (desired_new_size < new_size_before && eden()->is_empty()) {
    // bail out of shrinking if objects in eden
    size_t change = new_size_before - desired_new_size;
    assert(change % alignment == 0, "just checking");
    _virtual_space.shrink_by(change);
    changed = true;
  }

  if (changed) {
    // The spaces have already been mangled at this point but
    // may not have been cleared (set top = bottom) and should be.
    // Mangling was done when the heap was being expanded.
    compute_space_boundaries(eden()->used(),
                             SpaceDecorator::Clear,
                             SpaceDecorator::DontMangle);

    MemRegion cmr((HeapWord*)_virtual_space.low(),
                  (HeapWord*)_virtual_space.high());
    Universe::heap()->barrier_set()->resize_covered_region(cmr);
    if (Verbose && PrintGC) {
      size_t new_size_after  = _virtual_space.committed_size();
      size_t eden_size_after = eden()->capacity();
      size_t survivor_size_after = from()->capacity();
      gclog_or_tty->print("New generation size " SIZE_FORMAT "K->"
        SIZE_FORMAT "K [eden="
        SIZE_FORMAT "K,survivor=" SIZE_FORMAT "K]",
        new_size_before/K, new_size_after/K,
        eden_size_after/K, survivor_size_after/K);
      if (WizardMode) {
        gclog_or_tty->print("[allowed " SIZE_FORMAT "K extra for %d threads]",
          thread_increase_size/K, threads_count);
      }
      gclog_or_tty->cr();
    }
  }
}