Java对象内存分配原理及源码分析

澳门新葡亰手机版 1

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)" : "");
    }
  }
}

下面就以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.实例的创建首先需要知道该类型是否被加载和正确解析,根据字节码所指定的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

自我介绍

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