澳门新浦京娱乐游戏Java对象内存分配原理及源码分析

澳门新浦京娱乐游戏 3

Java对象的分配,根据其过程,将其分为快速分配和慢速分配两种形式,其中快速分配使用无锁的指针碰撞技术在新生代的Eden区上进行分配,而慢速分配根据堆的实现方式、GC的实现方式、代的实现方式不同而具有不同的分配调用层次。

对象的分配

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

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

一般我们得代码都是通过解释器执行,当创建对象得时候,解释器执行 new
指令,来到这里:openjdkhotspotsrcsharevminterpreterinterpreterRuntime.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
方法分配对象,虚拟机调用跳转到:openjdkhotspotsrcsharevmoopsinstanceKlass.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)来为对象分配内存,源码位置:openjdkhotspotsrcsharevmgc_interfacecollectedHeap.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
方法,在该方法中具体实现内存分配的细节,源码文件openjdkhotspotsrcsharevmmemorycollectorPolicy.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 nt"
                  " size=%d %s", try_count, size, is_tlab ? "(TLAB)" : "");
    }
  }
}

概述

在JDK1.9中移除了JDK 8中已弃用的垃圾收集器(GC)组合,官网原始文档如下:

Removes garbage collector (GC) combinations that were deprecated in
JDK 8.
This means that the following GC combinations no longer exist:

  1. DefNew + CMS
  2. ParNew + SerialOld
  3. Incremental CMS

The “foreground” mode for Concurrent Mark Sweep (CMS) has also been
removed. The following command-line flags have been removed:

  1. -Xincgc
  2. -XX:+CMSIncrementalMode
  3. -XX:+UseCMSCompactAtFullCollection
  4. -XX:+CMSFullGCsBeforeCompaction
  5. -XX:+UseCMSCollectionPassing

The command line flag -XX:+UseParNewGC no longer has an effect. ParNew
can only be used with CMS and CMS requires ParNew. Thus, the
-XX:+UseParNewGC flag has been deprecated and will likely be removed
in a future release.

在JDK1.9以后,将不再推荐使用CMS垃圾回收器,官方推荐使用 G1
垃圾回收器,官方原始文档如下:

Makes Garbage-First (G1) the default garbage collector (GC) on 32- and
64-bit >server configurations. Using a low-pause collector such as
G1 provides a better >overall experience, for most users, than a
throughput-oriented collector such as >the Parallel GC, which was
previously the default.

由此可见,在未来G1将成为JVM虚拟机中主流的垃圾回收器,接下来我们去了解一下在使用
G1 的情况下,对象的分配流程。

下面就以bytecodeInterpreter解释器对于new指令的解释出发,分析实例对象的内存分配过程:

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,源码地址:openjdkhotspotsrcsharevmruntimevm_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,源码地址:openjdkhotspotsrcsharevmgc_implementationsharedvmGCOperations.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
方法,在该方法中调用垃圾回收的相关方法。深入到该方法中,源码地址:openjdkhotspotsrcsharevmmemorygenCollectedHeap.cpp

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

获得程序设置的垃圾回收器类型,调用satisfy_failed_allocation方法,进行垃圾回收,查看关键代码,源码位置:openjdkhotspotsrcsharevmmemorycollectorPolicy.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

对象分配通有流程

对象的分配流程如下图:

  1. 判断对象是否已经加载,如果已经加载,执行步骤 3,否则执行步骤 2
  2. 通过类加载器加载类的字节码文件,对字节码文件进行验证、准备、解析等操作,进入步骤
    3
  3. 为对象分配内存空间,首先尝试在TLAB空间分配对象,如果分配失败,在eden空间分配
  4. 在eden空间分配对象,如果分配成功则对对象进行初始化,并将对象引用赋给变量,如果分配失败则触发GC,进行垃圾回收,对内存空间进行回收以后,重新尝试对象的分配。

以上是对象分配的通有流程,但是在采用不同垃圾回收器的情况下,对象的分配流程有所差异,下面我们来研究一下在使用G1的情况下,对象分配的流程是怎么样的。

一、快速分配

1.实例的创建首先需要知道该类型是否被加载和正确解析,根据字节码所指定的CONSTANT_Class_info常量池索引,获取对象的类型信息并调用is_unresovled_klass()验证该类是否被解析过,在创建类的实例之前,必须确保该类型已经被正确加载和解析。

 CASE(_new): {
        u2 index = Bytes::get_Java_u2(pc+1);
        constantPoolOop constants = istate->method()->constants();
        if (!constants->tag_at(index).is_unresolved_klass()) {

2.接下来获取该类型在虚拟机中的表示instanceKlass(具体可以参考前文实例探索Java对象的组织结构)

oop entry = constants->slot_at(index).get_oop();
          assert(entry->is_klass(), "Should be resolved klass");
          klassOop k_entry = (klassOop) entry;
          assert(k_entry->klass_part()->oop_is_instance(), "Should be instanceKlass");
          instanceKlass* ik = (instanceKlass*) k_entry->klass_part();

3.当类型已经被初始化并且可以被快速分配时,那么将根据UseTLAB来决定是否使用TLAB技术(Thread-Local
Allocation
Buffers,线程局部分配缓存技术)来将分配工作交由线程自行完成。TLAB是每个线程在Java堆中预先分配了一小块内存,当有对象创建请求内存分配时,就会在该块内存上进行分配,而不需要在Eden区通过同步控制进行内存分配。

if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
            size_t obj_size = ik->size_helper();
            oop result = NULL;
            // If the TLAB isn't pre-zeroed then we'll have to do it
            bool need_zero = !ZeroTLAB;
            if (UseTLAB) {
              result = (oop) THREAD->tlab().allocate(obj_size);
            }
            if (result == NULL) {
              need_zero = true;

4.如果不使用TLAB或在TLAB上分配失败,则会尝试在堆的Eden区上进行分配。Universe::heap()返回虚拟机内存体系所使用的CollectedHeap,其top_addr()返回的是Eden区空闲块的起始地址变量_top的地址,end_addr()是Eden区空闲块的结束地址变量_end的地址。故这里compare_to是Eden区空闲块的起始地址,new_top为使用该块空闲块进行分配后新的空闲块起始地址。这里使用CAS操作进行空闲块的同步操作,即观察_top的预期值,若与compare_to相同,即没有其他线程操作该变量,则将new_top赋给_top真正成为新的空闲块起始地址值,这种分配技术叫做bump-the-pointer(指针碰撞技术)。

 retry:
              HeapWord* compare_to = *Universe::heap()->top_addr();
              HeapWord* new_top = compare_to + obj_size;
              if (new_top <= *Universe::heap()->end_addr()) {
                if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
                  goto retry;
                }
                result = (oop) compare_to;
              }
            }

5.根据是否需要填0选项,对分配空间的对象数据区进行填0

if (result != NULL) {
              // Initialize object (if nonzero size and need) and then the header
              if (need_zero ) {
                HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;
                obj_size -= sizeof(oopDesc) / oopSize;
                if (obj_size > 0 ) {
                  memset(to_zero, 0, obj_size * HeapWordSize);
                }
              }

6.根据是否使用偏向锁,设置对象头信息,然后设置对象的klassOop引用(这样对象本身就获取了获取类型数据的途径)

if (UseBiasedLocking) {
                result->set_mark(ik->prototype_header());
              } else {
                result->set_mark(markOopDesc::prototype());
              }
              result->set_klass_gap(0);
              result->set_klass(k_entry);

7.把对象地址引入栈,并继续执行下一个字节码

SET_STACK_OBJECT(result, 0);
              UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);

8.若该类型没有被解析,就会调用InterpreterRuntime的_new函数完成慢速分配

// Slow case allocation
        CALL_VM(InterpreterRuntime::_new(THREAD, METHOD->constants(), index),
                handle_exception);
        SET_STACK_OBJECT(THREAD->vm_result(), 0);
        THREAD->set_vm_result(NULL);
        UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);

以上就是快速分配的过程,其流程图如下,关键在于快速分配在Eden区所使用的无锁指针碰撞技术

澳门新浦京娱乐游戏 1

自我介绍

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

G1对象分配

程序启动时,使用模板解释器来执行字节码,当执行new操作时,调用下面代码完成对象创建工作,源码地址:openjdkhotspotsrcsharevminterpreterinterpreterRuntime.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

在创建对象之前,从运行时常量池中获取 Klass
对象,调用check_valid_for_instantiation
方法,确保我们初始化的类不是一个抽象类,接下来检查对象是否已经加载(如果未加载,通过类加载器加载class),最后调用allocate_instance
方法,创建对象,接下来我们深入进去,查看源码具体实现。源码地址:openjdkhotspotsrcsharevmoopsinstanceKlass.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;
}

通过调用 instanceKlass::allocate_instance
方法,分配对象:首先判断类是否重写finalize()方法,获取对象需要分配的内存空间的大小,然后创建对象,如果之前判断对象重写了finalize()方法,将该对象注册到
Finalizer
队列里面。在该方法中调用CollectedHeap::obj_allocate方法创建对象,继续往下深入,源码地址:openjdkhotspotsrcsharevmgc_interfacecollectedHeap.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;
}

在上面的代码中,通过断言判断现在是否在执行GC,以及对象分配的大小是否大于0,如果断言通过,进行内存空间的分配,然后对对象进行初始化操作。关于对象的内存分配主要时调用CollectedHeap类的
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
方法分配了内存空间,然后调用 init_obj
方法对分配的内存进行填充。继续深入到
common_mem_allocate_noinit方法中,查看内存分配的详情。

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());
  }
}

上面贴了部分关键代码,首先对象会尝试在TLAB空间分配对象,如果分配失败,则尝试在Eden空间分配内存,如果在Eden空间也分配失败,就会抛出常见的OutOfMemoryError的异常,下面分别讲解如何在TLAB空间和Eden空间划分内存空间。

二、慢速分配

接下来看看慢速分配是如何进行的:

1.InterpreterRuntime的_new函数定义在/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp中:

IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, constantPoolOopDesc* pool, int index))
  klassOop k_oop = pool->klass_at(index, CHECK);
  instanceKlassHandle klass (THREAD, k_oop);

  // Make sure we are not instantiating an abstract klass
  klass->check_valid_for_instantiation(true, CHECK);

  // Make sure klass is initialized
  klass->initialize(CHECK);

  oop obj = klass->allocate_instance(CHECK);
  thread->set_vm_result(obj);
IRT_END

该函数在进行了对象类的检查(确保不是抽象类)和对该类型进行初始化后,调用instanceKlassHandle的allocate_instance进行内存分配。

其中instanceKlassHandle类由DEF_KLASS_HANDLE宏进行声明,注意该类重载了成员访问运算符”->”,这里的一系列成员方法的访问实际上是instanceKlass对象的访问。

 type*    operator -> () const       { return (type*)obj()->klass_part(); }

2.所以实际上是调用了instanceKlass的allocate_instance()成员函数:

allocate_instance()定义在/hotspot/src/share/vm/oops/instanceKlass.cpp

(1).检查是否设置了Finalizer函数,获取对象所需空间的大小

instanceOop instanceKlass::allocate_instance(TRAPS) {
     bool has_finalizer_flag = has_finalizer(); // Query before possible GC
     int size = size_helper();  // Query before forming handle.

(2).调用CollectedHeap的obj_allocate()创建一个instanceOop(堆上的对象实例),并根据情况注册Finalizer函数

    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;

3.CollectedHeap::ojb_allocate()定义在/hotspot/src/share/vm/gc_interface/CollectedHeap.hpp中,它将转而调用内联函数obj_allocate()

4.obj_allocate()定义在/hotspot/src/share/vm/gc_interface/CollectedHeap.inline.h中,若当正处于gc状态时,不允许进行内存分配申请,否则将调用common_mem_allocate_init()进行内存分配并返回获得内存的起始地址,随后将调用post_allocation_setup_obj()进行一些初始化工作

oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {
 //...assert
  HeapWord* obj = common_mem_allocate_init(size, false, CHECK_NULL);
  post_allocation_setup_obj(klass, obj, size);
  NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
  return (oop)obj;
}

5.common_mem_allocate_init()分为两部分,将分别调用common_mem_allocate_noinit()进行内存空间的分配和调用init_obj()进行对象空间的初始化

HeapWord* CollectedHeap::common_mem_allocate_init(size_t size, bool is_noref, TRAPS) {
  HeapWord* obj = common_mem_allocate_noinit(size, is_noref, CHECK_NULL);
  init_obj(obj, size);
  return obj;
}

6.common_mem_allocate_noinit()如下:

(1).若使用了本地线程分配缓冲TLAB,则会调用allocate_from_tlab()尝试从TLAB中分配内存

  HeapWord* result = NULL;
  if (UseTLAB) {
    result = CollectedHeap::allocate_from_tlab(THREAD, size);
    if (result != NULL) {
      assert(!HAS_PENDING_EXCEPTION,
             "Unexpected exception, will result in uninitialized storage");
      return result;
    }
  }

(2).否则会调用堆的mem_allocate()尝试分配

  bool gc_overhead_limit_was_exceeded = false;
  result = Universe::heap()->mem_allocate(size,
                                          is_noref,
                                          false,
                                          &gc_overhead_limit_was_exceeded);

(3).统计分配的字节数

 if (result != NULL) {
   //...
    THREAD->incr_allocated_bytes(size * HeapWordSize);
    return result;
  }

(4).否则说明申请失败,若在申请过程中gc没有超时,则抛出OOM异常

if (!gc_overhead_limit_was_exceeded) {
    // -XX:+HeapDumpOnOutOfMemoryError and -XX:OnOutOfMemoryError support
    report_java_out_of_memory("Java heap space");

    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP,
        "Java heap space");
    }

    THROW_OOP_0(Universe::out_of_memory_error_java_heap());

7.对象内存分配后的初始化过程包括两部分,一个是init_obj()完成对对象内存空间的对齐和填充,一个是post_allocation_setup_obj()对堆上的oop对象进行初始化。

(1).init_obj():

void CollectedHeap::init_obj(HeapWord* obj, size_t size) {
  assert(obj != NULL, "cannot initialize NULL object");
  const size_t hs = oopDesc::header_size();
  assert(size >= hs, "unexpected object size");
  ((oop)obj)->set_klass_gap(0);
  Copy::fill_to_aligned_words(obj + hs, size - hs);
}

hs就是对象头的大小,fill_to_aligned_words将对象空间除去对象头的部分做填0处理,该函数定义在/hotspot/src/share/vm/utilities/copy.h中,并转而调用pd_fill_to_aligned_words()。

pd_fill_to_aligned_words根据不同平台实现,以x86平台为例,该函数定义在/hotspot/src/cpu/x86/vm/copy_x86.h中:

static void pd_fill_to_words(HeapWord* tohw, size_t count, juint value) {
#ifdef AMD64
  julong* to = (julong*) tohw;
  julong  v  = ((julong) value << 32) | value;
  while (count-- > 0) {
    *to++ = v;
  }
#else
  juint* to = (juint*)tohw;
  count *= HeapWordSize / BytesPerInt;
  while (count-- > 0) {
    *to++ = value;
  }
#endif // AMD64
}

该函数的作用就是先将地址类型转换,然后把堆的字数转化为字节数,再对该段内存进行填值(value
= 0)处理

(2).post_allocation_setup_obj()调用了post_allocation_setup_common()进行初始化工作,然后调用post_allocation_notify()通知JVMTI和dtrace

void CollectedHeap::post_allocation_setup_obj(KlassHandle klass,
                                              HeapWord* obj,
                                              size_t size) {
  post_allocation_setup_common(klass, obj, size);
  assert(Universe::is_bootstrapping() ||
         !((oop)obj)->blueprint()->oop_is_array(), "must not be an array");
  // notify jvmti and dtrace
  post_allocation_notify(klass, (oop)obj);
}

post_allocation_setup_common()如下:

void CollectedHeap::post_allocation_setup_common(KlassHandle klass,
                                                 HeapWord* obj,
                                                 size_t size) {
  post_allocation_setup_no_klass_install(klass, obj, size);
  post_allocation_install_obj_klass(klass, oop(obj), (int) size);
}

post_allocation_setup_no_klass_install()根据是否使用偏向锁,设置对象头信息等,即初始化oop的_mark字段。post_allocation_install_obj_klass()设置对象实例的klassOop引用,即初始化oop的_metadata(_klass/_compressed_klass)字段

以上内容就是堆实现无关的慢速分配过程,其流程图如下:

澳门新浦京娱乐游戏 2

G1内存结构

在讲对象如何在TLAB空间和Eden空间分配之前,我们先了解一下使用G1垃圾回收器时,内存结构是什么样的。G1内存结构图如下:

在使用G1垃圾收集器的情况下,堆被划分为多个内存大小相等的Region,每个Region被划分到不同的年龄代中,下面我们结合源码,去查看一下在使用G1的情况下,Region的空间大小。源码地址:hotspotsrcsharevmgc_implementationg1heapRegion.cpp

//最小region大小
#define MIN_REGION_SIZE  (      1024 * 1024 )
//最大region大小
#define MAX_REGION_SIZE  ( 32 * 1024 * 1024 )
//region个数
#define TARGET_REGION_NUMBER          2048

void HeapRegion::setup_heap_region_size(uintx min_heap_size) {
  // region_size in bytes
  uintx region_size = G1HeapRegionSize;
  if (FLAG_IS_DEFAULT(G1HeapRegionSize)) {
    region_size = MAX2(min_heap_size / TARGET_REGION_NUMBER,
                       (uintx) MIN_REGION_SIZE);
  }

  int region_size_log = log2_long((jlong) region_size);
  //region_size是2的指数
  region_size = ((uintx)1 << region_size_log);

  // Now make sure that we don't go over or under our limits.
  if (region_size < MIN_REGION_SIZE) {
    region_size = MIN_REGION_SIZE;
  } else if (region_size > MAX_REGION_SIZE) {
    region_size = MAX_REGION_SIZE;
  }

  // And recalculate the log.
  region_size_log = log2_long((jlong) region_size);
  ....................................省略..........................................
}

从上面的代码可以看出,G1默认region的最小大小为1M,最大大小为32M,且每个region的大小是2^n次,同时默认将堆划分为2048块。

三、堆的分配实现

1.mem_allocate将由堆的实现类型定义,以GenCollectedHeap为例:

HeapWord* GenCollectedHeap::mem_allocate(size_t size,
                                         bool is_large_noref,
                                         bool is_tlab,
                                         bool* gc_overhead_limit_was_exceeded) {
  return collector_policy()->mem_allocate_work(size,
                                               is_tlab,
                                               gc_overhead_limit_was_exceeded);
}

2.由之前分析,GenCollectedHeap根据用户配置有着不同的GC策略(默认的和配置UseSerialGC的MarkSweepPolicy、配置UseComcMarkSweepGC和UseAdaptiveSizePolicy的ASConcurrentMarkSweepPolicy、只配置UseComcMarkSweepGC的ConcurrentMarkSweepPolicy),但这里,对象内存空间的基本结构和分配的思想是一致的,所以统一由GenCollectorPolicy实现进行分代层级的对象分配操作,但具体的工作将交由各代的实现者来完成。

GenCollectedPolicy的mem_allocate_work()函数如下:

(1).gch指向GenCollectedHeap堆,内存分配请求将循环不断地进行尝试,直到分配成功或GC后分配失败

HeapWord* GenCollectorPolicy::mem_allocate_work(size_t size,
                                        bool is_tlab,
                                        bool* gc_overhead_limit_was_exceeded) {
  GenCollectedHeap *gch = GenCollectedHeap::heap();
  //...
  // 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.
    Generation *gen0 = gch->get_gen(0);

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

若对象应该在新生代上分配,就会调用新生代的par_allocate()进行分配,注意在新生代普遍是采用复制收集器的,而内存的分配对应采用了无锁式的指针碰撞技术。

(2).在新生代上尝试无锁式的分配失败,那么就获取堆的互斥锁,并尝试在各代空间内进行内存分配

unsigned int gc_count_before;  // read inside the Heap_lock locked region
    {
      MutexLocker ml(Heap_lock);
     //...
      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;
      }

其中should_try_older_generation_allocation()如下:

bool GenCollectorPolicy::should_try_older_generation_allocation(
        size_t word_size) const {
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  size_t gen0_capacity = gch->get_gen(0)->capacity_before_gc();
  return    (word_size > heap_word_size(gen0_capacity))
         || GC_locker::is_active_and_needs_gc()
         || gch->incremental_collection_failed();
}

当进行gc前,新生代的空闲空间大小不足以分配对象,或者有线程触发了gc,或前一次的FullGC是由MinorGC触发的情况,都应该不再尝试再更高的内存代上进行分配,以保证新分配的对象尽可能在新生代空间上。

attempt_allocation()实现如下:

HeapWord* GenCollectedHeap::attempt_allocation(size_t size,
                                               bool is_tlab,
                                               bool first_only) {
  HeapWord* res;
  for (int i = 0; i < _n_gens; i++) {
    if (_gens[i]->should_allocate(size, is_tlab)) {
      res = _gens[i]->allocate(size, is_tlab);
      if (res != NULL) return res;
      else if (first_only) break;
    }
  }
  // Otherwise...
  return NULL;
}

即由低内存代向高内存代尝试分配内存

(3).从各个代空间都找不到可用的空闲内存(或不应该在更高的内存代上分配时),如果已经有线程触发了gc,那么当各代空间还有virtual
space可扩展空间可用时,将会尝试扩展代空间并再次尝试进行内存分配,有点在gc前想尽一切办法获得内存的意思。

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()) {
          // 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;
          }
        }

(4).否则各代已经没有可用的可扩展空间时,当当前线程没有位于jni的临界区时,将释放堆的互斥锁,以使得请求gc的线程可以进行gc操作,等待所有本地线程退出临界区和gc完成后,将继续循环尝试进行对象的内存分配

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;
        }

(5).若各代无法分配对象的内存,并且没有gc被触发,那么当前请求内存分配的线程将发起一次gc,这里将提交给VM一个GenCollectForAllocation操作以触发gc,当操作执行成功并返回时,若gc锁已被获得,那么说明已经由其他线程触发了gc,将继续循环以等待gc完成

VM_GenCollectForAllocation op(size,
                                  is_tlab,
                                  gc_count_before);
    VMThread::execute(&op);
    if (op.prologue_succeeded()) {
      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
      }

否则将等待gc完成,若gc超时则会将gc_overhead_limit_was_exceeded设置为true返回给调用者,并重置超时状态,并对分配的对象进行填充处理

    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;
      }

以上内容就是堆的实现相关、但代/GC实现无关的分配过程,其流程图归纳如下:

澳门新浦京娱乐游戏 3

TLAB空间对象分配

上面小节,我们分析了G1的内存结构,这小节我们分析对象如何在TLAB空间分配。通过调用
allocate_from_tlab 方法,实现在TLAB空间分配对象,代码如下:

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空间分配(通过指针碰撞算法分配对象),如果对象分配成功,则返回对象,如果分配失败,则进入慢分配流程。接下来我们分析一下慢分配流程:

HeapWord* CollectedHeap::allocate_from_tlab_slow(KlassHandle klass, Thread* thread, size_t size) {
  //TLAB空间剩余大小大于可忽略大小
  if (thread->tlab().free() > thread->tlab().refill_waste_limit()) {
    //重设可忽略大小
    thread->tlab().record_slow_allocation(size);
    return NULL;
  }
  //计算新TLAB空间大小
  size_t new_tlab_size = thread->tlab().compute_size(size);

  thread->tlab().clear_before_allocation();

  if (new_tlab_size == 0) {
    return NULL;
  }

  // 分配新的TLAB空间
  HeapWord* obj = Universe::heap()->allocate_new_tlab(new_tlab_size);
  if (obj == NULL) {
    return NULL;
  }
  ........................省略..........................
  //分配对象
  thread->tlab().fill(obj, obj + size, new_tlab_size);
  return obj;
}

在上面的代码中,先判断TLAB空间剩余的大小是否大于可忽略的大小,如果满足,就重新设置可忽略大小,如果不满足,重新申请一片TLAB空间,进行对象分配,在使用G1垃圾回收器的情况下,申请新TLAB空间的具体实现如下,源码地址:hotspotsrcsharevmgc_implementationg1g1CollectedHeap.cpp

HeapWord* G1CollectedHeap::allocate_new_tlab(size_t word_size) {
  assert_heap_not_locked_and_not_at_safepoint();
  assert(!isHumongous(word_size), "we do not allow humongous TLABs");

  unsigned int dummy_gc_count_before;
  return attempt_allocation(word_size, &dummy_gc_count_before);
}

上面的代码中主要是调用 attempt_allocation
方法分配内存空间,继续深入查看代码

G1CollectedHeap::attempt_allocation(size_t word_size,
                                    unsigned int* gc_count_before_ret) {
  assert_heap_not_locked_and_not_at_safepoint();
  assert(!isHumongous(word_size), "attempt_allocation() should not "
         "be called for humongous allocation requests");
  //_mutator_alloc_region内部持有一个引用_alloc_region,指向当前正活跃的eden region
  HeapWord* result = _mutator_alloc_region.attempt_allocation(word_size,

  //通过CAS分配对象失败,通过加锁分配内存                                                    false /* bot_updates */);
  if (result == NULL) {
    result = attempt_allocation_slow(word_size, gc_count_before_ret);
  }
  assert_heap_not_locked();
  if (result != NULL) {
    dirty_young_block(result, word_size);
  }
  return result;
}

从上面可以看出,通过 _mutator_alloc_region 对象分配TLAB空间,在
_mutator_alloc_region 对象中,有个 _alloc_region
指针,该指针指向当前活跃的 region
空间,如果分配失败,则通过加锁的方式来分配对象。接下我们查看一下attempt_allocation
方法的实现

inline HeapWord* G1AllocRegion::attempt_allocation(size_t word_size,
                                                   bool bot_updates) {
  assert(bot_updates == _bot_updates, ar_ext_msg(this, "pre-condition"));

  //获得当前活跃的region
  HeapRegion* alloc_region = _alloc_region;
  assert(alloc_region != NULL, ar_ext_msg(this, "not initialized properly"));

  //分配对象内存
  HeapWord* result = par_allocate(alloc_region, word_size, bot_updates);
  if (result != NULL) {
    trace("alloc", word_size, result);
    return result;
  }
  trace("alloc failed", word_size);
  return NULL;
}

在该方法中,我们看到了 _alloc_region 指针,该指针执行当前活跃的
region,然后调用 par_allocate 方法,分配TLAB空间, par_allocate
最终回调用 par_allocate_impl
方法,分配TLAB空间。源码地址:hotspotsrcsharevmmemoryspace.cpp

inline HeapWord* ContiguousSpace::par_allocate_impl(size_t size,
                                                    HeapWord* const end_value) {
  do {
    HeapWord* obj = top();
    if (pointer_delta(end_value, obj) >= size) {
      HeapWord* new_top = obj + size;
      //采用指针碰撞方式,分配对象内存(CAS)
      HeapWord* result = (HeapWord*)Atomic::cmpxchg_ptr(new_top, top_addr(), obj);
      // result can be one of two:
      //  the old top value: the exchange succeeded
      //  otherwise: the new value of the top is returned.
      if (result == obj) {
        assert(is_aligned(obj) && is_aligned(new_top), "checking alignment");
        return obj;
      }
    } else {
      return NULL;
    }
  } while (true);
}

如果通过 _mutator_alloc_region 对象分配TLAB空间失败,则会调用
attempt_allocation_slow 方法,通过该方法分配TLAB空间,源码如下:

HeapWord* G1CollectedHeap::attempt_allocation_slow(size_t word_size,
                                           unsigned int *gc_count_before_ret) {
  HeapWord* result = NULL;
  for (int try_count = 1; /* we'll return */; try_count += 1) {
    bool should_try_gc;
    unsigned int gc_count_before;
    {
      MutexLockerEx x(Heap_lock);//获取堆的锁

      result = _mutator_alloc_region.attempt_allocation_locked(word_size,
                                                      false /* bot_updates */);
      if (result != NULL) {
        return result;
      }
      //如果执行到这里,说明内存分配失败
      assert(_mutator_alloc_region.get() == NULL, "only way to get here");

      if (GC_locker::is_active_and_needs_gc()) {//JNI方法调用,执行扩容操作】
        //如果还有空闲的区域
        if (g1_policy()->can_expand_young_list()) {
          // No need for an ergo verbose message here,
          // can_expand_young_list() does this when it returns true.
          result = _mutator_alloc_region.attempt_allocation_force(word_size,
                                                      false /* bot_updates */);
          if (result != NULL) {
            return result;
          }
        }
        should_try_gc = false;
      } else {
        if (GC_locker::needs_gc()) {
          should_try_gc = false;
        } else {
          // Read the GC count while still holding the Heap_lock.
          gc_count_before = total_collections();
          should_try_gc = true;
        }
      }
    }

    if (should_try_gc) {
      bool succeeded;
      //触发GC
      result = do_collection_pause(word_size, gc_count_before, &succeeded);
      if (result != NULL) {
        assert(succeeded, "only way to get back a non-NULL result");
        return result;
      }

    ...............................中间省略部分代码..................................
    result = _mutator_alloc_region.attempt_allocation(word_size,
                                                      false /* bot_updates */);
    if (result != NULL) {
      return result;
    }
}

该方法篇幅较长,主要是先获得堆空间的锁,然后调用
attempt_allocation_locked
方法分配内存,如果分配失败,判断当前是否在执行JNI方法的调用,如果是执行扩容操作,如果不是则判断是否需要GC,如果
should_try_gc 为true,调用do_collection_pause
方法,触发一次GC操作。接下来,我们分析一下 attempt_allocation_locked
方法,源码如下:

inline HeapWord* G1AllocRegion::attempt_allocation_locked(size_t word_size,
                                                          bool bot_updates) {
  // First we have to tedo the allocation, assuming we're holding the
  // appropriate lock, in case another thread changed the region while
  // we were waiting to get the lock.
  HeapWord* result = attempt_allocation(word_size, bot_updates);
  if (result != NULL) {
    return result;
  }

  retire(true /* fill_up */);
  result = new_alloc_region_and_allocate(word_size, false /* force */);
  if (result != NULL) {
    trace("alloc locked (second attempt)", word_size, result);
    return result;
  }
  trace("alloc locked failed", word_size);
  return NULL;
}

从上面可以看出,会先通过调用 attempt_allocation
方法分配内存,由于上面已经分析过该方法,就不再次分析,如果调用
attempt_allocation 方法分配失败,则调用
new_alloc_region_and_allocate
方法,分配内存,具体实现如下,源码地址:hotspotsrcsharevmgc_implementationg1g1AllocRegion.cpp

HeapWord* G1AllocRegion::new_alloc_region_and_allocate(size_t word_size,
                                                       bool force) {
  assert(_alloc_region == _dummy_region, ar_ext_msg(this, "pre-condition"));
  assert(_used_bytes_before == 0, ar_ext_msg(this, "pre-condition"));
  trace("attempting region allocation");

  //分配一个新的region
  HeapRegion* new_alloc_region = allocate_new_region(word_size, force);
  if (new_alloc_region != NULL) {
    new_alloc_region->reset_pre_dummy_top();
    // Need to do this before the allocation
    _used_bytes_before = new_alloc_region->used();
    HeapWord* result = allocate(new_alloc_region, word_size, _bot_updates);
    assert(result != NULL, ar_ext_msg(this, "the allocation should succeeded"));

    OrderAccess::storestore();
    _alloc_region = new_alloc_region;
    _count += 1;
    trace("region allocation successful");
    return result;
  } else {
    trace("region allocation failed");
    return NULL;
  }
  ShouldNotReachHere();
}

该方法中调用了 allocate_new_region 方法分配新的 region,该方法最终调用
new_mutator_alloc_region 方法,分配region,具体实现如下:

HeapRegion* G1CollectedHeap::new_mutator_alloc_region(size_t word_size,
                                                      bool force) {
  assert_heap_locked_or_at_safepoint(true /* should_be_vm_thread */);
  assert(!force || g1_policy()->can_expand_young_list(),
         "if force is true we should be able to expand the young list");
  //判断当前young_list中的region数是否已经超过阈值
  bool young_list_full = g1_policy()->is_young_list_full();
  if (force || !young_list_full) {
    HeapRegion* new_alloc_region = new_region(word_size,
                                              false /* do_expand */);
    if (new_alloc_region != NULL) {
      set_region_short_lived_locked(new_alloc_region);
      _hr_printer.alloc(new_alloc_region, G1HRPrinter::Eden, young_list_full);
      return new_alloc_region;
    }
  }
  return NULL;
}

在 new_mutator_alloc_region
判断是否强强制分配内存,或者当前young_list中的region数是否已经超过阈值,如果判断成功,新分配一个region,如果失败,然后NULL;到此对象在TLAB空间如果分配的流程分析结束。

Eden空间分配

上一小节分析了在TLAB空间分配内存,如果在TLAB空间分配失败,则在Eden空间分配,在Eden空间分配主要是调用了下面方法:

 //在堆中分配
  result = Universe::heap()->mem_allocate(size,
                                          &gc_overhead_limit_was_exceeded);

在使用的G1垃圾收集器的情况下,内存被划分为多个region,将不同region划分给不同的年代,接下来分析在使用G1的情况下,对象如何分配,深入到
mem_allocate
方法中,源码地址:hotspotsrcsharevmgc_implementationg1g1CollectedHeap.cpp

HeapWord*
G1CollectedHeap::mem_allocate(size_t word_size,
                              bool*  gc_overhead_limit_was_exceeded) {
  assert_heap_not_locked_and_not_at_safepoint();

  for (int try_count = 1; /* we'll return */; try_count += 1) {
    unsigned int gc_count_before;

    HeapWord* result = NULL;
    if (!isHumongous(word_size)) {
      //在Region里面分配对象
      result = attempt_allocation(word_size, &gc_count_before);
    } else {
     //在region中分配大对象
      result = attempt_allocation_humongous(word_size, &gc_count_before);
    }
    if (result != NULL) {
      return result;
    }

    // 触发GC
    VM_G1CollectForAllocation op(gc_count_before, word_size);
    // ...and get the VM thread to execute it.
    VMThread::execute(&op);
    ..........................省略.............................
}

在 mem_allocate
方法中,判断分配的对象是否是巨型对象,如果不是巨型对象,调用
attempt_allocation,该方法在上一小节已经分析,如果是巨型对象,调用attempt_allocation_humongous
方法,分配内存空间,如果分配失败,这触发一次GC,因为之前已经分析了
attempt_allocation 方法,该小节就不分析了,我们主要分析
attempt_allocation_humongous
方法,关键代码如下,源码地址:hotspotsrcsharevmgc_implementationg1g1AllocRegion.cpp

{
      MutexLockerEx x(Heap_lock);

      //巨型对象不在年代代分配,先尝试分配
      result = humongous_obj_allocate(word_size);
      if (result != NULL) {
        return result;
      }

      if (GC_locker::is_active_and_needs_gc()) {
        should_try_gc = false;
      } else {
        //判断是否进行GC
        if (GC_locker::needs_gc()) {
          should_try_gc = false;
        } else {
          gc_count_before = total_collections();
          should_try_gc = true;
        }
      }
    }

    if (should_try_gc) {
      //触发GC
      bool succeeded;
      result = do_collection_pause(word_size, gc_count_before, &succeeded);
      if (result != NULL) {
        assert(succeeded, "only way to get back a non-NULL result");
        return result;
      }
   ....................省略..........................
}

在上面的方法中先尝试分配巨型对象,如果对象分配不成功,则判断是否触发GC,接下来我们分析
humongous_obj_allocate 方法的实现,关键代码如下:

  size_t word_size_rounded = round_to(word_size, HeapRegion::GrainWords);

 //需要的region数
  uint num_regions = (uint) (word_size_rounded / HeapRegion::GrainWords);
  uint x_num = expansion_regions();
  uint fs = _hrs.free_suffix();

  //从空闲可用的region列表中找到多个连续的region,并返回第一个region的序号
  uint first = humongous_obj_allocate_find_first(num_regions, word_size);
  if (first == G1_NULL_HRS_INDEX) {
    // The only thing we can do now is attempt expansion.
    if (fs + x_num >= num_regions) {
      assert(num_regions > fs, "earlier allocation should have succeeded");
      ergo_verbose1(ErgoHeapSizing,
                    "attempt heap expansion",
                    ergo_format_reason("humongous allocation request failed")
                    ergo_format_byte("allocation request"),
                    word_size * HeapWordSize);

      if (expand((num_regions - fs) * HeapRegion::GrainBytes)) {
        first = humongous_obj_allocate_find_first(num_regions, word_size);
      }
    }
  }

  HeapWord* result = NULL;
  if (first != G1_NULL_HRS_INDEX) {
    //分配内存
    result =
      humongous_obj_allocate_initialize_regions(first, num_regions, word_size);
    assert(result != NULL, "it should always return a valid result");
    //
    g1mm()->update_sizes();
  }

在该方法中,判断分配该巨型对象需要多少个region,并从空间的region列表中分配多个连续的region,返回第一个region的序号,接下来判断是否用那么多空闲的region,如果没有,进行扩容操作,接下来进行对象的分配。

参考链接

http://www.jianshu.com/p/a0efa489b99f

自我介绍

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

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图