对象的创建以及YoungGC的触发

对象的分配

大部分对象都在Heap(堆中进行分配),Heap空间是共享的内存空间,当多个线程在Heap中为对象分配内存空间时,需要通过加锁的方式进行同步,为了提高对象分配的效率,对象在线程TLAB空间为对象分配内存。对象分配流程图如下:


下面结合Hotspot源码来分析对象内存分配流程:

一般我们得代码都是通过解释器执行,当创建对象得时候,解释器执行 new 指令,来到这里:openjdk\hotspot\src\share\vm\interpreter\interpreterRuntime.cpp

IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, constantPoolOopDesc* pool, int index))

  //从运行时常量池中获取KlassOop
  klassOop k_oop = pool->klass_at(index, CHECK);
  instanceKlassHandle klass (THREAD, k_oop);

  // 确保我们没有实例化一个抽象的klass
  klass->check_valid_for_instantiation(true, CHECK);

  // 保证已经完成类加载和初始化
  klass->initialize(CHECK);

  //分配对象
  oop obj = klass->allocate_instance(CHECK);
  thread->set_vm_result(obj);
IRT_END

上面的代码中对创建的类的相关信息进行验证(是否对以后抽象类进行初始化,初始化的类是否加载),然后调用 allocate_instance 方法分配对象,虚拟机调用跳转到:openjdk\hotspot\src\share\vm\oops\instanceKlass.cpp

instanceOop instanceKlass::allocate_instance(TRAPS) {
  assert(!oop_is_instanceMirror(), "wrong allocation path");
  //是否重写finalize()方法
  bool has_finalizer_flag = has_finalizer(); // Query before possible GC
  //分配的对象的大小
  int size = size_helper();  // Query before forming handle.

  KlassHandle h_k(THREAD, as_klassOop());

  instanceOop i;

  //分配对象
  i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
  if (has_finalizer_flag && !RegisterFinalizersAtInit) {
    i = register_finalizer(i, CHECK_NULL);
  }
  return i;
}

上面代码主要判断类是否重写了finalize(),重写改方法的类是实例对象会加入finalize队列,队列里面的对象在GC前会调用finalize()方法,尝试重新建立引用,接下来调用size_helper()方法,计算需要分配的对象的空间大小。然后调用CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS)来为对象分配内存,源码位置:openjdk\hotspot\src\share\vm\gc_interface\collectedHeap.inline.hpp,具体代码如下:

//对象内存空间分配
oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {
  debug_only(check_for_valid_allocation_state());
  //校验在GC的时候不分配内存
  assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
  //分配大小大于0
  assert(size >= 0, "int won't convert to size_t");
  //内存分配
  HeapWord* obj = common_mem_allocate_init(klass, size, CHECK_NULL);
  //初始化
  post_allocation_setup_obj(klass, obj);
  NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
  return (oop)obj;
}

上面的代码中,对相关信息进行验证,然后调用 common_mem_allocate_init 方法分配内存,代码如下:

HeapWord* CollectedHeap::common_mem_allocate_init(KlassHandle klass, size_t size, TRAPS) {
  //申请内存
  HeapWord* obj = common_mem_allocate_noinit(klass, size, CHECK_NULL);
  //字节填充对齐
  init_obj(obj, size);
  return obj;
}

从上面的代码可以看出,对象内存的分配实际上是调用了common_mem_allocate_noinit 方法,在该方法中,会先尝试在TLAB空间中分配内存空间,(TLAB的相关资料可以参考:http://www.kejixun.com/article/170523/330012.shtml)如果失败在堆中分配,如果在堆中也分配失败,就会抛出OutOfMemoryError,关键代码如下:

HeapWord* CollectedHeap::common_mem_allocate_noinit(KlassHandle klass, size_t size, TRAPS) {
  .............................
  HeapWord* result = NULL;
  if (UseTLAB) {//在TLAB中分配
    result = allocate_from_tlab(klass, THREAD, size);
    if (result != NULL) {
      assert(!HAS_PENDING_EXCEPTION,
             "Unexpected exception, will result in uninitialized storage");
      return result;
    }
  }
  bool gc_overhead_limit_was_exceeded = false;
  //在堆中分配
  result = Universe::heap()->mem_allocate(size,
                                          &gc_overhead_limit_was_exceeded);
  if (result != NULL) {
    NOT_PRODUCT(Universe::heap()->
      check_for_non_bad_heap_word_value(result, size));
    assert(!HAS_PENDING_EXCEPTION,
           "Unexpected exception, will result in uninitialized storage");
    THREAD->incr_allocated_bytes(size * HeapWordSize);
    AllocTracer::send_allocation_outside_tlab_event(klass, size * HeapWordSize);

    return result;
  }
    ..............................
    THROW_OOP_0(Universe::out_of_memory_error_gc_overhead_limit());
  }
}

上面的代码段中,首先调用 allocate_from_tlab 方法,尝试在TLAB空间分配对象,如果内存分配失败,调用 mem_allocate 方法,在 eden 区中分配内存空间,下面分别来查看这两个方法的具体实现。

HeapWord* CollectedHeap::allocate_from_tlab(KlassHandle klass, Thread* thread, size_t size) {
  assert(UseTLAB, "should use UseTLAB");
  //TLAB分配
  HeapWord* obj = thread->tlab().allocate(size);
  if (obj != NULL) {
    return obj;
  }
  // Otherwise..
  //慢分配
  return allocate_from_tlab_slow(klass, thread, size);
}

在TLAB空间如果分配成功就直接返回该对象,如果TLAB空间不足,就会分配失败,调用 allocate_from_tlab_slow,重新申请一片TLAB空间进行内存的分配。

HeapWord* CollectedHeap::allocate_from_tlab_slow(KlassHandle klass, Thread* thread, size_t size) {

  // Retain tlab and allocate object in shared space if
  // the amount free in the tlab is too large to discard.
  //当tlab中剩余空间>设置的可忽略大小以及申请一块新的tlab失败时返回null,然后走上面的第二步,
  //也就是在堆的共享区域分配。当tlab剩余空间可以忽略,则申请一块新的tlab,若申请成功,则在此tlab上分配。 
  if (thread->tlab().free() > thread->tlab().refill_waste_limit()) {
    thread->tlab().record_slow_allocation(size);
    return NULL;
  }

  // Discard tlab and allocate a new one.
  // To minimize fragmentation, the last TLAB may be smaller than the rest.
  //重新申请一块TLAB
  size_t new_tlab_size = thread->tlab().compute_size(size);
  thread->tlab().clear_before_allocation();
  if (new_tlab_size == 0) {
    return NULL;
  }
  // 对象分配
  // Allocate a new TLAB...
  HeapWord* obj = Universe::heap()->allocate_new_tlab(new_tlab_size);
  if (obj == NULL) {
    return NULL;
  }

  AllocTracer::send_allocation_in_new_tlab_event(klass, new_tlab_size * HeapWordSize, size * HeapWordSize);
  ....................................
  return obj;
}

如果在TLAB空间分配失败,就会调用 mem_allocate 方法在eden空间分配内存,该方法内部通过调用 mem_allocate_work 方法,在该方法中具体实现内存分配的细节,源码文件openjdk\hotspot\src\share\vm\memory\collectorPolicy.cpp:

HeapWord* GenCollectorPolicy::mem_allocate_work(size_t size,
                                        bool is_tlab,
                                        bool* gc_overhead_limit_was_exceeded) {
  GenCollectedHeap *gch = GenCollectedHeap::heap();

  debug_only(gch->check_for_valid_allocation_state());
  assert(gch->no_gc_in_progress(), "Allocation during gc not allowed");

  // In general gc_overhead_limit_was_exceeded should be false so
  // set it so here and reset it to true only if the gc time
  // limit is being exceeded as checked below.
  *gc_overhead_limit_was_exceeded = false;

  HeapWord* result = NULL;

  // Loop until the allocation is satisified,
  // or unsatisfied after GC.
  for (int try_count = 1; /* return or throw */; try_count += 1) {
    HandleMark hm; // discard any handles allocated in each iteration

    // First allocation attempt is lock-free.
    //第一次尝试分配不需要获取锁,通过while+CAS来进行分配
    Generation *gen0 = gch->get_gen(0);
    assert(gen0->supports_inline_contig_alloc(),
      "Otherwise, must do alloc within heap lock");
    if (gen0->should_allocate(size, is_tlab)) {//对大小进行判断,比如是否超过eden区能分配的最大大小
      result = gen0->par_allocate(size, is_tlab);///while循环+指针碰撞+CAS分配
      if (result != NULL) {
        assert(gch->is_in_reserved(result), "result not in heap");
        return result;
      }
    }
    //如果res=null,表示在eden区分配失败了,因为没有连续的空间。则继续往下走
    unsigned int gc_count_before;  // read inside the Heap_lock locked region
    {
      MutexLocker ml(Heap_lock);//锁
      if (PrintGC && Verbose) {
        gclog_or_tty->print_cr("TwoGenerationCollectorPolicy::mem_allocate_work:"
                      " attempting locked slow path allocation");
      }
      // Note that only large objects get a shot at being
      // allocated in later generations.
       //需要注意的是,只有大对象可以被分配在老年代。一般情况下都是false,所以first_only=true
      bool first_only = ! should_try_older_generation_allocation(size);
      //在年轻代分配
      result = gch->attempt_allocation(size, is_tlab, first_only);
      if (result != NULL) {
        assert(gch->is_in_reserved(result), "result not in heap");
        return result;
      }
      /*Gc操作已被触发但还无法被执行,一般不会出现这种情况,只有在jni中jni_GetStringCritical等
      方法被调用时出现is_active_and_needs_gc=TRUE,主要是为了避免GC导致对象地址改变。
      jni_GetStringCritical方法的作用参考文章:http://blog.csdn.net/xyang81/article/details/42066665
      */
      if (GC_locker::is_active_and_needs_gc()) {
        if (is_tlab) {
          return NULL;  // Caller will retry allocating individual object
        }
        if (!gch->is_maximal_no_gc()) {////因为不能进行GC回收,所以只能尝试通过扩堆
          // Try and expand heap to satisfy request
          result = expand_heap_and_allocate(size, is_tlab);
          // result could be null if we are out of space
          if (result != NULL) {
            return result;
          }
        }

        // If this thread is not in a jni critical section, we stall
        // the requestor until the critical section has cleared and
        // GC allowed. When the critical section clears, a GC is
        // initiated by the last thread exiting the critical section; so
        // we retry the allocation sequence from the beginning of the loop,
        // rather than causing more, now probably unnecessary, GC attempts.
        JavaThread* jthr = JavaThread::current();
        if (!jthr->in_critical()) {
          MutexUnlocker mul(Heap_lock);
          // Wait for JNI critical section to be exited
          GC_locker::stall_until_clear();
          continue;
        } else {
          if (CheckJNICalls) {
            fatal("Possible deadlock due to allocating while"
                  " in jni critical section");
          }
          return NULL;
        }
      }

      // Read the gc count while the heap lock is held.
      gc_count_before = Universe::heap()->total_collections();
    }
    //VM操作进行一次由分配失败触发的GC
    VM_GenCollectForAllocation op(size,
                                  is_tlab,
                                  gc_count_before);
    VMThread::execute(&op);
    if (op.prologue_succeeded()) {////一次GC操作已完成
      result = op.result();
      if (op.gc_locked()) {
         assert(result == NULL, "must be NULL if gc_locked() is true");
         continue;  // retry and/or stall as necessary
      }

      // Allocation has failed and a collection
      // has been done.  If the gc time limit was exceeded the
      // this time, return NULL so that an out-of-memory
      // will be thrown.  Clear gc_overhead_limit_exceeded
      // so that the overhead exceeded does not persist.

    /* 
      分配失败且已经完成GC了,则判断是否超时等信息。
       */
      const bool limit_exceeded = size_policy()->gc_overhead_limit_exceeded();
      const bool softrefs_clear = all_soft_refs_clear();
      assert(!limit_exceeded || softrefs_clear, "Should have been cleared");
      if (limit_exceeded && softrefs_clear) {
        *gc_overhead_limit_was_exceeded = true;
        size_policy()->set_gc_overhead_limit_exceeded(false);
        if (op.result() != NULL) {
          CollectedHeap::fill_with_object(op.result(), size);
        }
        return NULL;
      }
      assert(result == NULL || gch->is_in_reserved(result),
             "result not in heap");
      return result;
    }

    // Give a warning if we seem to be looping forever.
    if ((QueuedAllocationWarningCount > 0) &&
        (try_count % QueuedAllocationWarningCount == 0)) {
          warning("TwoGenerationCollectorPolicy::mem_allocate_work retries %d times \n\t"
                  " size=%d %s", try_count, size, is_tlab ? "(TLAB)" : "");
    }
  }
}

YoungGC触发

在年轻代尝试对象的分配,如果对象分配失败,就触发一次YoungGC,YoungGC的触发是通过创建一个VM_GenCollectForAllocation,调用VMThread的 execute 方法来触发一次YoungGC。进入execute方法,由于execute方法太长,下面只贴关键部分,源码地址:

if (op->evaluate_at_safepoint() && !SafepointSynchronize::is_at_safepoint()) {
      SafepointSynchronize::begin();//驱使所有线程进入safepoint然后挂起他们
      op->evaluate();//调用vm_operation的doit()方法进行回收
      SafepointSynchronize::end();////唤醒所有的线程,在safepoint执行之后,让这些线程重新恢复执行
 } else {
      op->evaluate();
 }

调用VM_Operation的 evaluate,源码地址:openjdk\hotspot\src\share\vm\runtime\vm_operations.cpp

void VM_Operation::evaluate() {
  ResourceMark rm;
  if (TraceVMOperation) {
    tty->print("[");
    NOT_PRODUCT(print();)
  }
  //实际进行操作的方法
  doit();
  if (TraceVMOperation) {
    tty->print_cr("]");
  }
}

主要是调用了VM_GenCollectForAllocation的 doit() 方法进行GC,源码地址:openjdk\hotspot\src\share\vm\gc_implementation\shared\vmGCOperations.cpp

void VM_GenCollectForAllocation::doit() {
  SvcGCMarker sgcm(SvcGCMarker::MINOR);

  GenCollectedHeap* gch = GenCollectedHeap::heap();
  GCCauseSetter gccs(gch, _gc_cause);
  //通知内存堆管理器处理一次内存分配失败
  _res = gch->satisfy_failed_allocation(_size, _tlab);//res=分配的结果,垃圾回收过程
  assert(gch->is_in_reserved_or_null(_res), "result not in heres=分配的结果ap");

  if (_res == NULL && GC_locker::is_active_and_needs_gc()) {
    set_gc_locked();
  }
}

从上面的代码可以看出是调用satisfy_failed_allocation 方法,在该方法中调用垃圾回收的相关方法。深入到该方法中,源码地址:openjdk\hotspot\src\share\vm\memory\genCollectedHeap.cpp

HeapWord* GenCollectedHeap::satisfy_failed_allocation(size_t size, bool is_tlab) {
  return collector_policy()->satisfy_failed_allocation(size, is_tlab);
}

获得程序设置的垃圾回收器类型,调用satisfy_failed_allocation方法,进行垃圾回收,查看关键代码,源码位置:openjdk\hotspot\src\share\vm\memory\collectorPolicy.cpp

if (GC_locker::is_active_and_needs_gc()) {////表示有jni在操作内存,此时不能进行GC避免改变对象在内存的位置
    // GC locker is active; instead of a collection we will attempt
    // to expand the heap, if there's room for expansion.
    if (!gch->is_maximal_no_gc()) {
      result = expand_heap_and_allocate(size, is_tlab);//扩堆
    }
    return result;   // could be null if we are out of space
    
    //consult_young=true的时候,表示调用该方法时,判断此时晋升是否的安全的。
    //若=false,表示只取上次young gc时设置的参数,此次不再进行额外的判断。
  } else if (!gch->incremental_collection_will_fail(false /* don't consult_young */)) {
    // Do an incremental collection.
    gch->do_collection(false            /* full */,
                       false            /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  } else {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print(" :: Trying full because partial may fail :: ");
    }
    // Try a full collection; see delta for bug id 6266275
    // for the original code and why this has been simplified
    // with from-space allocation criteria modified and
    // such allocation moved out of the safepoint path.
    gch->do_collection(true             /* full */,
                       false            /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  }

  result = gch->attempt_allocation(size, is_tlab, false /*first_only*/);

  if (result != NULL) {
    assert(gch->is_in_reserved(result), "result not in heap");
    return result;
  }

调用GenCollectedHeap::do_collection 方法进行垃圾回收,该方法代码太长,截取关键代码:

_gens[i]->collect(full, do_clear_all_soft_refs, size, is_tlab);

下面主要查看 DefNewGeneration 进行垃圾回收的代码,对应的是SerialGC垃圾回收器,关键代码如下:

//寻找GCRoots
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);

  //从GCRoots进行遍历,标记存活的对象
  evacuate_followers.do_void();

关于YoungGC的具体执行算法可以参考:http://hllvm.group.iteye.com/group/topic/39376
http://www.jianshu.com/p/9af1a63a33c3

自我介绍

我是何勇,现在重庆猪八戒,多学学!!!

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

推荐阅读更多精彩内容