34534

发布时间:2024年01月06日

在10.2节中介绍垃圾回收线程时说过,当触发YGC时会产生一个VM_GenCollectFor-Allocation类型的任务,VMThread线程会调用VM_GenCollectForAllocation::doit()函数执行这个任务。在doit()函数中调用GenCollectorPolicy::satisfy_failed_allocation()函数处理内存申请失败的YGC请求,此函数会继续调用do_collecton()函数。Serial收集器通过调用do_collection()函数完成YGC,函数声明如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/genCollectedHeap.cpp
void GenCollectedHeap::do_collection(
    bool    full,
    bool    clear_all_soft_refs,
    size_t  size,
    bool    is_tlab,
    int     max_level
);

在进行YGC时,各个参数的值解释如下:

  • full值为false,表示进行YGC;
  • clear_all_soft_refs的值为false,表示不处理软引用,因为触发YGC时并不能说明内存紧张,只有执行FGC时才可能处理软引用;
  • size表示在请求分配此大小的内存时,由于空间不足而触发本次YGC;
  • is_tlab的值为true时,表示在为新的TLAB分配内存时触发此次垃圾回收,通过调用GenCollectedHeap::allocate_new_tlab()函数触发。
  • max_level在执行YGC和FGC时都为1,因为max_level的值就表示老年代,没有比老年代更高的代存在了。

do_collection()函数会根据传递的参数及其他判断条件来决定是只执行YGC还是只执行FGC,或者可能执行完YGC后再执行FGC。下面分几个部分介绍。

do_collection()函数的第一部分代码如下:

const bool do_clear_all_soft_refs = clear_all_soft_refs ||
                         collector_policy()->should_clear_all_soft_refs();

检查是否需要在本次GC时回收所有的软引用。MarkSweepPolicy回收策略会根据具体的GC情况在特定时刻决定是否对软引用指向的referent进行回收。注意,不同的回收策略执行回收任务时使用的策略可能不一样,有的可能是空间不够的时候回收软引用,有的可能是GC过于频繁或者过慢的时候会回收软引用,这里我们只讨论Serial/Serial Old收集器的默认回收策略MarkSweepPolicy。

在执行YGC时,clear_all_soft_refs参数的值为false,因为YGC不能说明内存紧张。在MarkSweepPolicy回收策略下,调用collector_policy()函数会获取CollectorPolicy类中的_should_clear_all_soft_refs属性的值,这个值在每次GC完成后会设置为false,因此最终不会清除软引用。

do_collection()函数的第二部分代码如下:

// 只有执行FGC时complete的值才为true
bool complete = full && (max_level == (n_gens()-1));
gc_prologue(complete);

调用gc_prologue()函数后最终会调用CollectedHeap::ensure_parsability()函数,ensure_parsability()函数会让每个线程的TLAB变得可解析,这种操作在9.2.1节中介绍过,当某个TLAB中的剩余空间不足以满足分配请求并且是可丢弃的情况下,向这个剩余空间中填充可解析的Object对象或整数类型数组,内存会变得连续。另外,各个TLAB中可能还有没有分配完的剩余空间,这些剩余空间都需要填充。ensure_parsability()函数的实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/gc_interface/collectedHeap.hpp
void CollectedHeap::ensure_parsability(bool retire_tlabs) {
  const bool use_tlab = UseTLAB;
  // 将每个线程中的最后一个TLAB中的剩余空间填充对象或数组
  for (JavaThread *thread = Threads::first(); thread; thread = thread->
next()) {
    if (use_tlab)
       thread->tlab().make_parsable(retire_tlabs);
    ...
  }
}

通常会将TLAB末尾尚未分配给Java对象的空间填充一个整数类型的数组,make_parsable()函数在9.2.1节中介绍过,这里不再赘述。

do_collection()函数的第三部分代码如下:

int starting_level = 0;
if (full) {
   for (int i = max_level; i >= 0; i--) {
       if (_gens[i]->full_collects_younger_generations()) {
         starting_level = i;
         break;
       }
   }
}

如果是FGC,那么在回收高的内存代时也会回收比自己低的内存代。这个逻辑主要是计算starting_level属性的值,这样_gens[0]到_gens[starting_level]所代表的内存代就会由本次GC负责回收。对于Serial/Serial Old收集器组合来说,新生代用DefNewGeneration实例表示,老年代用TenuredGeneration实例表示。当进行FGC时,max_level的值为1,而最终starting_level的值也为1,也就是FGC同时回收年轻代和老年代。

TenuredGeneration类中的full_collects_younger_generations()函数的实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/generation.hpp
virtual bool full_collects_younger_generations() const {
   return !CollectGen0First;
}

CollectGen0First的默认值为false,表示在回收老年代时顺便回收比老年代年轻的代,因此也会回收年轻代。

对于DefNewGeneration来说,没有重写full_collects_younger_generations()函数,因此会调用Generation类的默认实现,默认直接返回false,因为此年轻代没有比自己更年轻的代需要回收。

do_collection()函数的第四部分代码如下:

// 对于YGC来说,starting_level的值为0;对于FGC来说,starting_level的值为1
// 对于YGC来说,max_level的值为1,因此优先执行gen[0]内存代的回收,如果回收后仍不
// 满足,则触发此次YGC操作的内存分配请求,那么还会对gen[1]进行回收,此时执行的就是FGC
for (int i = starting_level; i <= max_level; i++) {
     if (_gens[i]->should_collect(full, size, is_tlab)) {
       ...
       record_gen_tops_before_GC();

       // 执行垃圾回收的工作
       {                                             // 匿名块开始
        HandleMark hm;
        // 为_saved_mark_word变量赋值为碰撞指针_top的值
        save_marks();
        ...
       // 执行真正的垃圾回收工作
        _gens[i]->collect(full, do_clear_all_soft_refs, size, is_tlab);

        ...
       } // 匿名块结束

       // 检查本次GC完成后,是否能满足触发本次GC回收的内存分配请求。如果能满足,则
       // size参数的值为0,那么下次在调用更高内存代的should_collect()函数时就会
       // 返回false;否则会返回true,继续执行高内存代的垃圾回收,以求回收更多的垃
       // 圾来满足这次内存分配请求
       if (size > 0) {
        if (!is_tlab || _gens[i]->supports_tlab_allocation()) {
          if (size*HeapWordSize <= _gens[i]->unsafe_max_alloc_nogc()) {
             size = 0;
          }
        }
       }
       ...
   }
}

对于YGC来说,starting_level的值为0,max_level的值为1,所以优先执行gen[0]内存代的回收,如果回收后仍不满足触发此次YGC操作的内存分配请求,那么还会对gen[1]进行回收,此时执行的就是FGC。之前已经多次介绍过,在配置了-XX:+UseSerialGC命令后,_gens[0]为DefNewGeneration实例,_gens[1]为TenuredGeneration实例。使用Serial/Serial Old收集器后,Java堆的内存布局如图11-2所示。

011-02

图11-2 使用Serial/Serial Old收集器时堆的布局

在执行YGC之前,需要调用DefNewGeneation类中的should_collect()函数判断此年轻代是否支持此次内存分配请求,如果不支持,那么YGC执行完成后也不能达到目的,还要继续执行FGC,因此在执行YGC之前要做如下判断:

源代码位置:openjdk/hotspot/src/share/vm/memory/generation.hpp
virtual bool should_collect(
        bool    full,
        size_t  word_size,
        bool    is_tlab
){
   return (full || should_allocate(word_size, is_tlab));
}

在执行YGC时,full参数的值为false,会继续调用should_allocate()函数进行判断。这个函数在9.2.2节中介绍过,主要判断3个条件,这3个条件必须同时满足,函数才会返回true,这3个条件如下:

  • 申请的内存大小未溢出;
  • 申请的内存大小不为0;
  • 支持TLAB内存分配,如果不支持TLAB内存分配,那么申请的内存大小没有超过本内存代的限制阈值。

当触发的是YGC时,调用DefNewGeneration类的should_allocate()函数通常会返回true,然后调用GenCollectedHeap::save_marks()函数执行垃圾回收前的准备工作,代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/genCollectedHeap.cpp
void GenCollectedHeap::save_marks() {
  for (int i = 0; i < _n_gens; i++) {
    _gens[i]->save_marks();
  }
}

年代代的save_marks()函数的实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/defNewGeneration.cpp
void DefNewGeneration::save_marks() {
  eden()->set_saved_mark();
  to()->set_saved_mark();
  from()->set_saved_mark();
}

老年代的save_marks()函数的实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/generation.cpp
void OneContigSpaceCardGeneration::save_marks() {
  _the_space->set_saved_mark();
}

源代码位置:openjdk/hotspot/src/share/vm/memory/space.hpp
virtual void set_saved_mark(){
  // 获取ContiguousSpace::_top属性值并赋值给Space类中
   // 定义的_saved_mark_word属性
   _saved_mark_word = top();
}

调用save_marks()相关函数后,各个函数的指向如图11-3所示。

011-03

图11-3 执行YGC(复制算法)之前各变量的指向

_saved_mark_word和_top等变量会辅助复制算法完成年轻代的垃圾回收,相关变量的介绍已经在10.1.2节中详细讲过,这里不再赘述。

下面就可以在do_collection()函数中执行真正的垃圾回收工作了,这部分内容将在下一节详细介绍。

YGC的处理流程如图11-4所示。

011-04

图11-4 YGC的处理流程

DefNewGeneration::collect()函数的实现逻辑比较多,下面分几个部分来解读,代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/defNewGeneration.cpp
void DefNewGeneration::collect(
      bool     full,
      bool     clear_all_soft_refs,
      size_t   size,
      bool     is_tlab
){
  // 确保当前是一次FGC,或者需要分配的内存size大于0,否则不需要执行一次GC操作
  assert(full || size > 0, "otherwise we don't want to collect");

  GenCollectedHeap* gch = GenCollectedHeap::heap();

  // 使用-XX:+UseSerialGC命令后,DefNewGeneration::_next_gen为TenuredGeneration
  _next_gen = gch->next_gen(this);

  // 如果新生代全是需要晋升的存活对象,老年代可能容不下这些对象,此时设置增量垃圾回收
  // 失败,直接返回,后续会执行FGC
  if (!collection_attempt_is_safe()) {
   // 设置_incremental_collection_failed为true,即放弃当前YGC
   // 通知内存堆管理器不要再尝试增量式GC了,因为肯定会失败,执行FGC
   gch->set_incremental_collection_failed();
   return;
  }
  // 在执行YGC时使用的是复制算法,因此要保存To Survivor区为空
  assert(to()->is_empty(), "Else not collection_attempt_is_safe");

  // 主要设置DefNewGeneration::_promotion_failed变量的值为false
  init_assuming_no_promotion_failure();
  ...
}

在执行YGC时,必须要通过老年代来保证晋升的对象,如果无法保证,则设置_incremental_collection_failed变量的值为true,然后放弃执行当前YGC,进行FGC。_incremental_collection_failed变量会在Java堆初始化时调用clear_incremental_collection_failed()函数将此值初始化为false。

通过collection_attempt_is_safe()函数判断当前的GC是否安全,实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/defNewGeneration.cpp
bool DefNewGeneration::collection_attempt_is_safe() {
  // To Survivor空间如果不为空,则无法采用复制算法,也就无法执行YGC
  if (!to()->is_empty()) {
    return false;
  }
  // 设置年轻代的下一个代为老年代
  if (_next_gen == NULL) {
    GenCollectedHeap* gch = GenCollectedHeap::heap();
    _next_gen = gch->next_gen(this);
  }
  // 调用used()函数获取当前年轻代已经使用的所有内存
  return _next_gen->promotion_attempt_is_safe(used());
}

安全的GC必须同时满足下面两个条件:

  • survivor中的to区为空,只有这样才能执行YGC的复制算法进行垃圾回收;
  • 下一个内存代有足够的内存容纳新生代的所有对象,因为年轻代需要老年代作为内存空间担保,如果老年代没有足够的内存空间作为担保,那么这次的YGC是不安全的。

调用used()函数的实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/defNewGeneration.cpp
size_t DefNewGeneration::used() const {
  return eden()->used() + from()->used();
}

将Eden区和From Survivor区已使用的内存空间加起来即为年轻代总的使用空间。有了年轻代总的使用容量后,调用TenuredGeneration::promotion_attempt_is_safe()函数来判断老年代是否能作为内存空间担保,这里假设年轻代的所有对象都需要晋升。promotion_attempt_is_safe()函数的实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/tenuredGeneration.cpp
bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion
_in_bytes) const {
  size_t  available = max_contiguous_available();
  size_t  av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average();
  bool    res = (available >= av_promo) || (available >= max_promotion
_in_bytes);
  return  res;
}

根据之前的GC数据获取平均的晋升空间,优先判断可用空间是否大于等于这个平均的晋升空间,其次判断是否大于等于最大的晋升空间max_promotion_in_bytes,只要有一个条件为真,函数就会返回true,表示这是一次安全的GC。对于满足可用空间大于等于平均晋升空间这个条件来说,函数返回true后,YGC在执行过程中可能会遇到分配担保失败的情况,因为实际的晋升空间如果大于平均晋升空间时就会失败,此时就需要执行FGC操作了。

这里还需要介绍一下TenuredGeneration::promotion_attempt_is_safe()函数中调用的用来计算最大连续可用空间的max_contiguous_available()函数,代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/generation.cpp
size_t Generation::max_contiguous_available() const {
  size_t max = 0;
  // 从当前代或比当前代更高的内存代中找出连续空闲空间的最大值,由于当前老年代已经没有
  // 更高的内存代,所以只是找出老年代的连续空闲空间
  for (const Generation* gen = this; gen != NULL; gen = gen->next_gen()) {
   size_t avail = gen->contiguous_available();
   if (avail > max) {
       max = avail;
   }
  }
  return max;
}

调用OneContigSpaceCardGeneration::contiguous_available()函数计算老年代的连续空闲空间,实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/generation.cpp
size_t OneContigSpaceCardGeneration::contiguous_available() const {
  return    _the_space->free() +
          _virtual_space.uncommitted_size();
}

源代码位置:openjdk/hotspot/src/share/vm/runtime/virtualspace.cpp
size_t VirtualSpace::uncommitted_size()  const {
  return reserved_size() - committed_size();
}

size_t VirtualSpace::reserved_size() const {
  return pointer_delta(high_boundary(), low_boundary(), sizeof(char));
}

size_t VirtualSpace::committed_size() const {
  return pointer_delta(high(), low(), sizeof(char));
}

在计算连续的空闲空间时,一定要加上虚拟内存空间中还未提交使用的空间,这块空间和空闲空间不但连续,而且当晋升的对象在已有的空闲空间中存储不下时,还会进行内存代的扩容,此时就会将未提交使用的空间囊括进来。

在DefNewGeneration::collect()函数中调用init_assuming_no_promotion_failure()函数的实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/defNewGeneration.hpp
void DefNewGeneration::init_assuming_no_promotion_failure() {
  _promotion_failed = false;
  _promotion_failed_info.reset();
  // 将CompactibleSpace::_next_compaction_space属性的值设置为NULL
  from()->set_next_compaction_space(NULL);
}

在以上代码中将_promotion_failed变量的值设置为false。其实通过查看Tenured-Generation::promotion_attempt_is_safe()函数后可以知道,晋升是有可能失败的。如果在YGC过程中失败,那么这个变量的值会设置为true。

继续看DefNewGeneration::collect()函数的实现,在开始回收对象之前还要做GC准备工作,具体如下:

  • 初始化IsAliveClosure闭包,该闭包封装了判断对象是否存活的逻辑;
  • 初始化ScanWeakRefClosure闭包,该闭包封装了扫描弱引用的逻辑,这里暂时不介绍;
  • 清空ageTable数据和To Survivor空间,ageTable会辅助判断对象晋升的条件,而保证To Survivor空间为空是执行复制算法的必备条件;
  • 初始化FastScanClosure,此闭包封装了存活对象的标识和复制逻辑。

初始化IsAliveClosure和ScanWeakRefClosure,代码如下:

IsAliveClosure     is_alive(this);
ScanWeakRefClosure  scan_weak_ref(this);

清空ageTable数据和To Survivor空间的代码如下:

ageTable* at = age_table();
at->clear();
to()->clear(SpaceDecorator::Mangle);

ageTable类的定义如下:

源代码位置:openjdk/hotspot/src/share/vm/gc_implementation/shared/ageTable.hpp
class ageTable VALUE_OBJ_CLASS_SPEC {
 public:
  enum {
     table_size = markOopDesc::max_age + 1  // table_size=16
  };

  size_t   sizes[table_size];               // 某个年龄代对象的总大小
  ...
}

年轻代中经历了多次YGC之后仍然没有被回收的对象就会晋升到老年代中。在第2章中介绍markOop时详细介绍过对象头中的一些信息,其中就包括锁状态在正常情况下存储的age:每经历一次YGC,对象的age就会加1。而到了某一时刻,如果对象的年龄大于设置的晋升的阈值,该对象就会晋升到老年代中。对象的年龄最大只能是15,因为age中只使用4个位来表示对象的年龄。

年轻代到老年代的晋升过程的判断如下:

1. 长期存活的对象进入老年代

虚拟机给每个对象定义了一个对象年龄计数器。如果对象在Eden空间分配并经过第一次YGC后仍然存活,在将对象移动到To Survivor空间后对象年龄会设置为1。对象在Survivor空间每熬过一次,YGC年龄就加一岁,当它的年龄增加到一定程度(默认为15岁)时,就会晋升到老年代中。对象晋升老年代的年龄阈值,可以通过-XX:MaxTenuring-Threshold选项来设置。ageTable类中定义table_size数组的大小为16,由于通过-XX:Max-TenuringThreshold选项可设置的最大年龄为15,所以数组的大小需要设置为16,因为还需要通过sizes[0]表示一次都未移动的对象,不过实际上不会统计sizes[0],因为sizes[0]的值一直为0。

2. 动态对象年龄判定

为了能更好地适应不同程度的内存状况,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代。如果在Survivor空间中小于等于某个年龄的所有对象空间的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。因此需要通过sizes数组统计年轻代中各个年龄对象的总空间。

调用clear()函数初始化sizes数组,实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/gc_implementation/shared/ageTable.cpp
void ageTable::clear() {
  // 初始化sizes数组中的值为0
for (size_t* p = sizes; p < sizes + table_size; ++p) {
     *p = 0;
  }
}

在清空To Survivor空间时会调用ContiguousSpace::clear()函数,实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/space.cpp
void ContiguousSpace::clear(bool mangle_space) {
  // 将Space::_bottom的值赋值给ContiguousSpace::_top
  set_top(bottom());
  // 将ContiguousSpace::_top的值赋值给Space::_saved_mark_word
  set_saved_mark();
  CompactibleSpace::clear(mangle_space);
}

void CompactibleSpace::clear(bool mangle_space) {
  ...
  _compaction_top = bottom();
}

将To Survivor空间的_saved_mark_word、_compaction_top和_top都赋值为_bottom属性的值。

调用GenCollectedHeap类的gen_process_strong_roots()函数将当前代上的根对象复制到转移空间To Survivor中,代码如下:

gch->gen_process_strong_roots(
       _level,
        true,
        true,
        true,                      // 进行对象复制
        SharedHeap::ScanningOption(so),
        &fsc_with_no_gc_barrier,   // 类型为FastScanClosure
        true,                      // walk *all* scavengable nmethods
        &fsc_with_gc_barrier,      // 类型为FastScanClosure
        &klass_scan_closure        // 类型为KlassScanClosure
    );

// 递归处理根集对象的引用对象,然后复制活跃对象到新的存储空间
evacuate_followers.do_void();

调用GenCollectedHeap类的gen_process_strong_roots()函数会标记复制根的所有直接引用对象,因此这个函数中最主要的实现就是找出所有的根引用并标记复制。接着还需要调用DefNewGeneration::FastEvacuateFollowersClosure::do_void()函数,此函数会从已经标记的直接由根引用的对象出发,按广度遍历算法处理间接引用的对象,这样就完成了年轻代所有对象的处理。调用时涉及两个函数,这两个函数是YGC实现垃圾回收的核心函数,下一节将详细介绍。

当将存活的对象复制到老年代中时,有可能因为老年代的空间有限而导致晋升失败,此时会将_promotion_failed属性的值设置为true。如果晋升成功,则清空Eden和From Survivor空间,然后交换From Survivor和To Survivor的角色,DefNewGeneration::collect()函数在完成对象标记和复制后的逻辑代码如下:

if (!_promotion_failed) {
   // 清空Eden和From Survivor空间,因为这两个空间中剩余的没有被移动的对象都是死
   // 亡对象
   eden()->clear(SpaceDecorator::Mangle);
   from()->clear(SpaceDecorator::Mangle);

   // 交换From Survivor和To Survivor的角色,这样已经清空的From Survivor空
   // 间会变为下一次回收的To Survivor空间
   swap_spaces();

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

   // 动态计算晋升阈值
   adjust_desired_tenuring_threshold();

   // 当YGC成功后,重新计算GC超时的时间计数
   AdaptiveSizePolicy* size_policy = gch->gen_policy()->size_policy();
   size_policy->reset_gc_overhead_limit_count();

   assert(!gch->incremental_collection_failed(), "Should be clear");
}
else { // 若发生了晋升失败,即老年代没有足够的内存空间用以存放新生代所晋升的所有对象
   _promo_failure_scan_stack.clear(true);

   // 恢复晋升失败对象的markOop,因为晋升失败的对象的转发地址已经指向自己
   remove_forwarding_pointers();

   // 当晋升失败时,虽然会交换From Survivor和To Survivor的角色,但是并不会清空
   // Eden和From Survivor空间,而会恢复晋升失败部分的对象头(加上To Survivor
   // 空间中的对象就是全部活跃对象了),这样在随后触发的FGC中能够对From Survivor
   // 和To Survivor空间进行压缩处理
   swap_spaces();
   // 设置From Survivor的下一个压缩空间为To Survivor,由于晋升失败会触发FGC,
   // 所以FGC会将Eden、From Survivor和To Survivor空间的活跃对象压缩在Eden
   // 和From Survivor空间
   from()->set_next_compaction_space(to());

   // 设置堆的YGC失败标记,并通知老年代晋升失败,之前在介绍DefNewGeneration::
   // collect()函数时讲过,当collection_attempt_is_safe()函数返回true时也有
   // 可能晋升失败,就是因为此函数的判断条件中含有可用空间是否大于等于平均的晋升空间。
   // 在实际执行YGC时,晋升量大于平均晋升量并且可用空间小于这个晋升空间时,会导致YGC
   // 失败
   gch->set_incremental_collection_failed();
}

在YGC回收的过程中,最终执行成功与否通过_promotion_failed变量的值来判断。如果成功,则清空Eden和From Survivor区,然后交换From Survivor和To Survivor的角色即可;如果失败,则需要触发FGC,如果不触发FGC将Eden、From Survivor和To Survivor的活跃对象压缩在Eden和From Survivor空间,那么后续就不能发生YGC,因为复制算法找不到一个可用的空闲空间。

在清空Eden和From Survivor空间时,分别会调用EdenSpace与ContiguousSpace的clear()函数,实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/space.cpp

void EdenSpace::clear(bool mangle_space) {
  ContiguousSpace::clear(mangle_space);
  set_soft_end(end());
}

void ContiguousSpace::clear(bool mangle_space) {
  set_top(bottom());
  set_saved_mark();               // 将_top值赋值给_saved_mark_word
  CompactibleSpace::clear(mangle_space);
}

void CompactibleSpace::clear(bool mangle_space) {
  ...
  _compaction_top = bottom();     // 获取Space::_bottom属性的值并赋值
}

在上面的代码中初始化了复制算法中使用的一些重要变量,相关变量的介绍已经在10.1.2节中详细讲过,这里不再赘述。

调用swap_space()函数交换From Survivor和To Survivor空间的角色,实现代码如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/defNewGeneration.cpp
void DefNewGeneration::swap_spaces() {
  // 简单交换From Survivor和To Survivor空间的首地址即可
  ContiguousSpace* s = from();
  _from_space       = to();
  _to_space         = s;

  // Eden空间的下一个压缩空间为From Survivor,FGC在压缩年轻代时通常会压缩这两个
  // 空间。如果YGC晋升失败,则From Survivor的下一个压缩空间是To Survivor,因此
  // FGC会压缩整理这三个空间
  eden()->set_next_compaction_space(from());
  from()->set_next_compaction_space(NULL);
}

swap_spaces()函数通过_from_space和_to_space区分和定位From Survivor与To Survivor空间。YGC在执行失败或成功的情况下都会调用以上函数,尤其是在失败的情况下定会触发FGC,然后使用老年代的标记-压缩算法来整理年轻代空间。

当YGC执行成功,清空Eden和From Survivor空间并交换From Survivor和To Survivor空间的角色后,下一步是调整年轻代活跃对象的晋升阈值。

源代码位置:openjdk/hotspot/src/share/vm/memory/defNewGeneration.cpp
void DefNewGeneration::adjust_desired_tenuring_threshold() {
   ageTable* at = age_table();
   size_t x = to()->capacity()/HeapWordSize;
   _tenuring_threshold = at->compute_tenuring_threshold(x);
}

源代码位置:openjdk/hotspot/src/share/vm/gc_implementation/shared/ageTable.cpp
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
  // 设置期望的Survivor大小为实际的Survivor大小的一半
  size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*
TargetSurvivorRatio)/100);
  size_t total = 0;
  uint age = 1;
  assert(sizes[0] == 0, "no objects with age zero should be recorded");
  while (age < table_size) {            // table_size的值为16
   total += sizes[age];
   // 如果所有小于等于age的对象总容量大于期望值,则直接跳出
   if (total > desired_survivor_size)
       break;
   age++;
  }

  // MaxTenuringThreshold默认的值为15
  uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
  return result;
}

-XX:TargetSurvivorRatio选项表示To Survivor空间占用百分比。调用adjust_desired_tenuring_threshold()函数是在YGC执行成功后,所以此次年轻代垃圾回收后所有的存活对象都被移动到了To Survivor空间内。如果To Survivor空间内的活跃对象的占比较高,会使下一次YGC时To Survivor空间轻易地被活跃对象占满,导致各种年龄代的对象晋升到老年代。为了解决这个问题,每次成功执行YGC后需要动态调整年龄阈值,这个年龄阈值既可以保证To Survivor空间占比不过高,也能保证晋升到老年代的对象都是达到了这个年龄阈值的对象。

因此,如果在Survivor区中存活的对象比较多,那么晋升阈值可能会变小,当下一次回收时,大于晋升阈值的对象都会晋升到老年代。

文章来源:https://blog.csdn.net/qq_38611252/article/details/135424137
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。