HotSpot note (part-3)

part 3

DefNew的GC属于Minor GC,使用copying算法进行垃圾收集,是Serial GC(-XX:+UseSerialGC)的新生代部分,接下来分析一下Serial GC的老年代部分,也就是Serial Old;TenuredGeneration是Serial Old的堆实现,这里还是要说一下什么情况下可能会发生Old GC,在分析DefNew的时候提到了所谓的"空间分配担保",也就是YoungGen在即将进行Minor GC的时候,让OldGen判断一下是否可以进行这次Minor GC,判断的方法是OldGen可用的连续空间大于新生代的对象大小或者大于新生代历史晋升的平均大小,如果这个条件成立的话,那么Minor GC就会进行,否则就会进行一次Major GC;下面将以TenuredGeneration的实现来分析一下OldGC的实现细节。
TenuredGeneration使用标记-整理算法进行垃圾收集,包括标记、压缩、清理三个核心步骤,TenuredGeneration::collect是TenuredGeneration垃圾收集工作的入口:

void TenuredGeneration::collect(bool   full,
                                bool   clear_all_soft_refs,
                                size_t size,
                                bool   is_tlab) {
  GenCollectedHeap* gch = GenCollectedHeap::heap();

  // Temporarily expand the span of our ref processor, so
  // refs discovery is over the entire heap, not just this generation
  ReferenceProcessorSpanMutator
    x(ref_processor(), gch->reserved_region());

  STWGCTimer* gc_timer = GenMarkSweep::gc_timer();
  gc_timer->register_gc_start();

  SerialOldTracer* gc_tracer = GenMarkSweep::gc_tracer();
  gc_tracer->report_gc_start(gch->gc_cause(), gc_timer->gc_start());

  gch->pre_full_gc_dump(gc_timer);

  GenMarkSweep::invoke_at_safepoint(ref_processor(), clear_all_soft_refs);

  gch->post_full_gc_dump(gc_timer);

  gc_timer->register_gc_end();

  gc_tracer->report_gc_end(gc_timer->gc_end(), gc_timer->time_partitions());
}

主要关注GenMarkSweep::invoke_at_safepoint函数调用,这是整个TennredGeneration垃圾收集的核心,invoke_at_safepoint函数通过调用下面四个函数来做具体的垃圾收集工作。

  // Mark live objects
  static void mark_sweep_phase1(bool clear_all_softrefs);
  // Calculate new addresses
  static void mark_sweep_phase2();
  // Update pointers
  static void mark_sweep_phase3();
  // Move objects to new positions
  static void mark_sweep_phase4();

下面根据每个步骤分别来分析一下具体的GC过程。

  • (1)、首先是标记阶段:mark_sweep_phase1

<img width="1018" alt="2018-11-11 5 21 54" src="https://user-images.githubusercontent.com/16225796/48311252-f7a03080-e5d7-11e8-9625-f51dd53c3ce4.png">

来看看full_process_roots函数的具体情况:

void GenCollectedHeap::full_process_roots(StrongRootsScope* scope,
                                          bool is_adjust_phase,
                                          ScanningOption so,
                                          bool only_strong_roots,
                                          OopsInGenClosure* root_closure,
                                          CLDClosure* cld_closure) {
  MarkingCodeBlobClosure mark_code_closure(root_closure, is_adjust_phase);
  OopsInGenClosure* weak_roots = only_strong_roots ? NULL : root_closure;
  CLDClosure* weak_cld_closure = only_strong_roots ? NULL : cld_closure;

  process_roots(scope, so, root_closure, weak_roots, cld_closure, weak_cld_closure, &mark_code_closure);
  if (is_adjust_phase) {
    // We never treat the string table as roots during marking
    // for the full gc, so we only need to process it during
    // the adjust phase.
    process_string_table_roots(scope, root_closure);
  }

  _process_strong_tasks->all_tasks_completed(scope->n_threads());
}

process_roots是需要重点关注的函数,这个函数将扫描出所有可以作为GCRoot的对象,扫描的地方非常多,可以参考下面这个代码片段:

<img width="958" alt="2018-11-11 5 40 11" src="https://user-images.githubusercontent.com/16225796/48311321-0d622580-e5d9-11e8-8d89-6b249bf95c8c.png">

这个strong_roots就是上面提到的follow_root_closure,他负责标记存活的对象,去它对应的do_oop函数看看到底是怎么做的:

void MarkSweep::FollowRootClosure::do_oop(oop* p)       {
  follow_root(p);
}
template <class T> inline void MarkSweep::follow_root(T* p) {
  assert(!Universe::heap()->is_in_reserved(p),
         "roots shouldn't be things within the heap");
  T heap_oop = oopDesc::load_heap_oop(p);
  if (!oopDesc::is_null(heap_oop)) {
    oop obj = oopDesc::decode_heap_oop_not_null(heap_oop);
    if (!obj->mark()->is_marked() &&
        !is_archive_object(obj)) {
      mark_object(obj);
      follow_object(obj);
    }
  }
  follow_stack();
}

对象是否被标记过时存储在对象头里面的,如果一个对象没有被标记过,就会用mark_object将会标记一个对象,具体看看mark_object的实现:

inline void MarkSweep::mark_object(oop obj) {
#if INCLUDE_ALL_GCS
  if (G1StringDedup::is_enabled()) {
    // We must enqueue the object before it is marked
    // as we otherwise can't read the object's age.
    G1StringDedup::enqueue_from_mark(obj);
  }
#endif
  // some marks may contain information we need to preserve so we store them away
  // and overwrite the mark.  We'll restore it at the end of markSweep.
  markOop mark = obj->mark();
  obj->set_mark(markOopDesc::prototype()->set_marked());

  if (mark->must_be_preserved(obj)) {
    preserve_mark(obj, mark);
  }
}

调用了oop的set_mark方法进行对象标记,如果对象头里面的信息需要被保存起来稍后GC完成需要恢复,那么就要调用preserve_mark将对象头的信息存储起来,mark是对象的oldMark,厦门市hipreserve_mark的实现:

// We preserve the mark which should be replaced at the end and the location
// that it will go.  Note that the object that this markOop belongs to isn't
// currently at that address but it will be after phase4
void MarkSweep::preserve_mark(oop obj, markOop mark) {
  // We try to store preserved marks in the to space of the new generation since
  // this is storage which should be available.  Most of the time this should be
  // sufficient space for the marks we need to preserve but if it isn't we fall
  // back to using Stacks to keep track of the overflow.
  if (_preserved_count < _preserved_count_max) {
    _preserved_marks[_preserved_count++].init(obj, mark);
  } else {
    _preserved_mark_stack.push(mark);
    _preserved_oop_stack.push(obj);
  }
}

这个函数较为简单,如果_preserved_marks里面存储了太多的对象头信息超出限制了,那么就将对象头信息分别存储在_preserved_mark_stack和_preserved_oop_stack两个栈里面,否则存储在_preserved_marks里面去;说完了对象的标记,下面来看看follow_object;

inline void MarkSweep::follow_object(oop obj) {
  assert(obj->is_gc_marked(), "should be marked");
  if (obj->is_objArray()) {
    // Handle object arrays explicitly to allow them to
    // be split into chunks if needed.
    MarkSweep::follow_array((objArrayOop)obj);
  } else {
    obj->oop_iterate(&mark_and_push_closure);
  }
}

follow_object根据名字可以猜测是处理obj的引用,我想这也是一个递归的过程,具体看看上面的代码片段,如果对象是一个数组对象,那么就使用follow_array来处理,否则使用对象的oop_iterate函数来处理,数组对象单独处理的原因是如果数组对象和普通对象一起处理,数组对象非常大的时候可能会影响普通对象的处理;follow_array最后依然还是使用follow_object来处理数组元素中的对象的,看看follow_array:

inline void MarkSweep::follow_array(objArrayOop array) {
  MarkSweep::follow_klass(array->klass());
  // Don't push empty arrays to avoid unnecessary work.
  if (array->length() > 0) {
    MarkSweep::push_objarray(array, 0);
  }
}

如果数组长度大于0,那么就使用push_objarray来处理这个数组:

void MarkSweep::push_objarray(oop obj, size_t index) {
  ObjArrayTask task(obj, index);
  assert(task.is_valid(), "bad ObjArrayTask");
  _objarray_stack.push(task);
}

push_objarray将数组push到了_objarray_stack栈里面,follow_stack函数会去处理_objarray_stack栈中的数组对象:

void MarkSweep::follow_stack() {
  do {
    while (!_marking_stack.is_empty()) {
      oop obj = _marking_stack.pop();
      assert (obj->is_gc_marked(), "p must be marked");
      follow_object(obj);
    }
    // Process ObjArrays one at a time to avoid marking stack bloat.
    if (!_objarray_stack.is_empty()) {
      ObjArrayTask task = _objarray_stack.pop();
      follow_array_chunk(objArrayOop(task.obj()), task.index());
    }
  } while (!_marking_stack.is_empty() || !_objarray_stack.is_empty());
}

从_marking_stack中拿出数组对象之后,调用follow_object继续处理,但是这时候follow_object里面的已经不是一个纯粹的数组对象了,已经是一个ObjArrayTask对象了,具体的标记过程泰国复杂就不继续深入了。
对象标记的工作完成之后,就要开始处理发现的对象了:

<img width="883" alt="2018-11-11 6 12 44" src="https://user-images.githubusercontent.com/16225796/48311639-b743b100-e5dd-11e8-9dd6-3c1d6ad1fe0d.png">

这个处理过程和Minor GC时的处理是一样的;接下来会做一些清理工作:

<img width="867" alt="2018-11-11 6 14 53" src="https://user-images.githubusercontent.com/16225796/48311697-b2333180-e5de-11e8-8eea-145af21b01b5.png">

SystemDictionary::do_unloading用于卸载一些不再使用到的类;CodeCache::do_unloading用于卸载一些不再使用到的方法(编译好的方法会放在CodeCache里面去);Klass::clean_weak_klass_links用于清理weak reference;StringTable::unlink(&is_alive)用于删除一些不再使用的字符串常量;SymbolTable::unlink()用于从符号表中清理那些不再使用的符号;

  • (2)、接着是mark_sweep_phase2,重新计算存活对象的新地址

首先看prepare_for_compaction这个函数:

void GenCollectedHeap::prepare_for_compaction() {
  // Start by compacting into same gen.
  CompactPoint cp(_old_gen);
  _old_gen->prepare_for_compaction(&cp);
  _young_gen->prepare_for_compaction(&cp);
}

首先看oldGen的prepare_for_compaction函数实现:

void Generation::prepare_for_compaction(CompactPoint* cp) {
  // Generic implementation, can be specialized
  CompactibleSpace* space = first_compaction_space();
  while (space != NULL) {
    space->prepare_for_compaction(cp);
    space = space->next_compaction_space();
  }
}

这是一个循环处理过程,通过prepare_for_compaction函数来处理:

void ContiguousSpace::prepare_for_compaction(CompactPoint* cp) {
  scan_and_forward(this, cp);
}

scan_and_forward这个函数名字非常直观,扫描并且做forward,forward可以理解为将对象转移到一个新的位置,整个步骤(2)只是计算出一个对象的新地址,并没有将对象转移到新的地址去,转移对象到新地址的工作将在接下来的步骤(3)里面进行,下面的代码片段是步骤(2)处理的核心:

 while (cur_obj < scan_limit) {
    assert(!space->scanned_block_is_obj(cur_obj) ||
           oop(cur_obj)->mark()->is_marked() || oop(cur_obj)->mark()->is_unlocked() ||
           oop(cur_obj)->mark()->has_bias_pattern(),
           "these are the only valid states during a mark sweep");
    if (space->scanned_block_is_obj(cur_obj) && oop(cur_obj)->is_gc_marked()) {
      // prefetch beyond cur_obj
      Prefetch::write(cur_obj, interval);
      size_t size = space->scanned_block_size(cur_obj);
      compact_top = cp->space->forward(oop(cur_obj), size, cp, compact_top);
      cur_obj += size;
      end_of_live = cur_obj;
    } else {
      // run over all the contiguous dead objects
      HeapWord* end = cur_obj;
      do {
        // prefetch beyond end
        Prefetch::write(end, interval);
        end += space->scanned_block_size(end);
      } while (end < scan_limit && (!space->scanned_block_is_obj(end) || !oop(end)->is_gc_marked()));

      // see if we might want to pretend this object is alive so that
      // we don't have to compact quite as often.
      if (cur_obj == compact_top && dead_spacer.insert_deadspace(cur_obj, end)) {
        oop obj = oop(cur_obj);
        compact_top = cp->space->forward(obj, obj->size(), cp, compact_top);
        end_of_live = end;
      } else {
        // otherwise, it really is a free region.

        // cur_obj is a pointer to a dead object. Use this dead memory to store a pointer to the next live object.
        *(HeapWord**)cur_obj = end;

        // see if this is the first dead region.
        if (first_dead == NULL) {
          first_dead = cur_obj;
        }
      }

      // move on to the next object
      cur_obj = end;
    }
  }

这个代码较长,主要完成的就一件事情,就是找到那些存活的对象,然后给这些存活的对象计算一个新的地址;为对象计算新地址的工作由CompactibleSpace::forward完成:

<img width="946" alt="2018-11-11 7 23 01" src="https://user-images.githubusercontent.com/16225796/48312302-95e7c280-e5e7-11e8-9eaf-48056e417a96.png">

CompactibleSpace::forward首先试图找到一块合适的内存来存放存活的对象,然后判断这块内存是否和存活对象目前所在的位置一样,如果一样的话就没必要移动了,否则就要改变指针来移动对象,移动的工作将在(3)中进行。

上面说了对存活对象的处理,对于死亡对象,首先找到下一个存活的对象,也就是找到一段连续的死亡对象,然后判断是否可以将这段死亡对象也当成是"活的"对象,判断条件还是比较严格的,首先,这段死亡对象的起点应该是compact_top,也就是空闲的空间起点(对于forward来说),并且通过dead_spacer.insert_deadspace的校验:

 bool insert_deadspace(HeapWord* dead_start, HeapWord* dead_end) {
    if (!_active) {
      return false;
    }

    size_t dead_length = pointer_delta(dead_end, dead_start);
    if (_allowed_deadspace_words >= dead_length) {
      _allowed_deadspace_words -= dead_length;
      CollectedHeap::fill_with_object(dead_start, dead_length);
      oop obj = oop(dead_start);
      obj->set_mark(obj->mark()->set_marked());

      assert(dead_length == (size_t)obj->size(), "bad filler object size");
      log_develop_trace(gc, compaction)("Inserting object to dead space: " PTR_FORMAT ", " PTR_FORMAT ", " SIZE_FORMAT "b",
          p2i(dead_start), p2i(dead_end), dead_length * HeapWordSize);

      return true;
    } else {
      _active = false;
      return false;
    }
  }

};

_allowed_deadspace_words是允许死亡对象存储的空间大小,这部分空间是属于浪费调的,如果太大那就不行了,那为什么还要将死亡对象也当成"活着"的对象对待呢?因为对象拷贝也是有损耗的,如果一段死亡对象刚好不需要移动,并且浪费掉的空间在可以接受的范围内,那么何乐而不为呢?insert_deadspace这个方法就是做这件事情的,当然,这段死亡的对象会被使用一个新的长度和原来这段死亡对象长度相等的一个对象替换。

  • (3)、步骤(2)完成了存活对象的新地址计算,那么步骤(3)的主要工作就是将对象转移到新的地址去,也算是"整理"了;

转移对象到新地址的工作由AdjustPointerClosure来完成,直接来看这个Closure的do_oop方法吧;

void MarkSweep::AdjustPointerClosure::do_oop(oop* p)       { do_oop_nv(p); }
void MarkSweep::AdjustPointerClosure::do_oop_nv(T* p)      { adjust_pointer(p); }

接着看adjust_pointer这个函数;

template <class T> inline void MarkSweep::adjust_pointer(T* p) {
  T heap_oop = oopDesc::load_heap_oop(p);
  if (!oopDesc::is_null(heap_oop)) {
    oop obj     = oopDesc::decode_heap_oop_not_null(heap_oop);
    assert(Universe::heap()->is_in(obj), "should be in heap");

    oop new_obj = oop(obj->mark()->decode_pointer());
    assert(is_archive_object(obj) ||                  // no forwarding of archive objects
           new_obj != NULL ||                         // is forwarding ptr?
           obj->mark() == markOopDesc::prototype() || // not gc marked?
           (UseBiasedLocking && obj->mark()->has_bias_pattern()),
           // not gc marked?
           "should be forwarded");
    if (new_obj != NULL) {
      if (!is_archive_object(obj)) {
        assert(Universe::heap()->is_in_reserved(new_obj),
              "should be in object space");
        oopDesc::encode_store_heap_oop_not_null(p, new_obj);
      }
    }
  }
}

adjust_pointer这个函数的目的是将对象p转移到new_obj里面去,在实现上,就是将new_obj的地址赋值给p即可:

static inline void encode_store_heap_oop_not_null(oop* p, oop v) { 
        *p = v;
 }
  • (4)、将所有存活的对象转移到新的地址里面去

GenCompactClosure会遍历老年代和新生代,做内存整理的工作;generation_iterate会根据配置从老年代或者新生代开始进行压缩工作:

void GenCollectedHeap::generation_iterate(GenClosure* cl,
                                          bool old_to_young) {
  if (old_to_young) {
    cl->do_generation(_old_gen);
    cl->do_generation(_young_gen);
  } else {
    cl->do_generation(_young_gen);
    cl->do_generation(_old_gen);
  }
}

下面是GenCompactClosure的do_generation函数:

  void do_generation(Generation* gen) {
    gen->compact();
  }
void Generation::compact() {
  CompactibleSpace* sp = first_compaction_space();
  while (sp != NULL) {
    sp->compact();
    sp = sp->next_compaction_space();
  }
}

跟着CompactibleSpace的compact函数看,CompactibleSpace::scan_and_compact是具体实现压缩工作的函数,下面来分析一下这个方法的实现细节;

(1)、如果这块内存内部没有存活的对象,那么可以直接忽略这块内容
(2)、找到第一个被GC标记过的对象,然后开始存活进行对象转移操作,对于遍历过程中遇到的没有被标记过的对象,skip;所谓转移就是通过内存对象拷贝将对象拷贝到计算好的地址去
(3)、最后需要判断是否这块内存已经空了,如果已经没有存活的对象了,那么清空整个region

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

推荐阅读更多精彩内容

  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,650评论 0 7
  • young generation garbage collection 整理 DefNew, ParNew, PS...
    andersonoy阅读 1,302评论 0 1
  • 转载blog.csdn.net/ning109314/article/details/10411495/ JVM工...
    forever_smile阅读 5,356评论 1 56
  • java基础volidate、线程生命周期、反射、NIO 内存分区GC、类加载 强弱等引用 基本数据结构 线程池 ...
    RichardLee123阅读 280评论 0 3
  • 简介 JVM是Java Virtural Machine(Java 虚拟机)的缩写,JVM是一种用于计算设备的规范...
    你需要一台永动机阅读 297评论 0 0