欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

源码分析:Java对象的内存分配(1)(2)

来源: javaer 分享于  点击 41679 次 点评:166

二、慢速分配

接下来看看慢速分配是如何进行的: 
1.InterpreterRuntime的_new函数定义在/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp中:

  1. IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, constantPoolOopDesc* pool, int index)) 
  2.   klassOop k_oop = pool->klass_at(index, CHECK); 
  3.   instanceKlassHandle klass (THREAD, k_oop); 
  4.  
  5.   // Make sure we are not instantiating an abstract klass 
  6.   klass->check_valid_for_instantiation(true, CHECK); 
  7.  
  8.   // Make sure klass is initialized 
  9.   klass->initialize(CHECK); 
  10.  
  11.   oop obj = klass->allocate_instance(CHECK); 
  12.   thread->set_vm_result(obj); 
  13. 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函数,获取对象所需空间的大小

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

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

  1. KlassHandle h_k(THREAD, as_klassOop()); 
  2.  
  3.       instanceOop i; 
  4.  
  5.       i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL); 
  6.       if (has_finalizer_flag && !RegisterFinalizersAtInit) { 
  7.         i = register_finalizer(i, CHECK_NULL); 
  8.       } 
  9.       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()进行一些初始化工作 

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

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

  1. HeapWord* CollectedHeap::common_mem_allocate_init(size_t size, bool is_noref, TRAPS) { 
  2.   HeapWord* obj = common_mem_allocate_noinit(size, is_noref, CHECK_NULL); 
  3.   init_obj(obj, size); 
  4.   return obj; 

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

  1. HeapWord* result = NULL; 
  2. if (UseTLAB) { 
  3.   result = CollectedHeap::allocate_from_tlab(THREAD, size); 
  4.   if (result != NULL) { 
  5.     assert(!HAS_PENDING_EXCEPTION, 
  6.            "Unexpected exception, will result in uninitialized storage"); return result; 
  7.   } 
(2).否则会调用堆的mem_allocate()尝试分配
  1. bool gc_overhead_limit_was_exceeded = false
  2.  result = Universe::heap()->mem_allocate(size, 
  3.                                          is_noref, 
  4.                                          false
  5.                        &gc_overhead_limit_was_exceeded); 

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

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

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

  1. if (!gc_overhead_limit_was_exceeded) { 
  2.     // -XX:+HeapDumpOnOutOfMemoryError and -XX:OnOutOfMemoryError support 
  3.     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"); 
  4.     } 
  5.  
  6.     THROW_OOP_0(Universe::out_of_memory_error_java_heap()); 

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

(1).init_obj():

  1. void CollectedHeap::init_obj(HeapWord* obj, size_t size) { 
  2.   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); 
  3. }

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中:

  1. static void pd_fill_to_words(HeapWord* tohw, size_t count, juint value) { 
  2. #ifdef AMD64 
  3.   julong* to = (julong*) tohw; 
  4.   julong  v  = ((julong) value << 32) | value; 
  5.   while (count-- > 0) { 
  6.     *to++ = v; 
  7.   } 
  8. #else 
  9.   juint* to = (juint*)tohw; 
  10.   count *= HeapWordSize / BytesPerInt; 
  11.   while (count-- > 0) { 
  12.     *to++ = value; 
  13.   } 
  14. #endif // AMD64 

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

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

  1. void CollectedHeap::post_allocation_setup_obj(KlassHandle klass, 
  2.                                               HeapWord* obj, 
  3.                                               size_t size) { 
  4.   post_allocation_setup_common(klass, obj, size); 
  5.   assert(Universe::is_bootstrapping() || 
  6.          !((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()如下:

  1. void CollectedHeap::post_allocation_setup_common(KlassHandle klass, 
  2.                                                  HeapWord* obj, 
  3.                                                  size_t size) { 
  4.   post_allocation_setup_no_klass_install(klass, obj, size); 
  5.   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)字段 。

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

三、堆的分配实现

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

  1. HeapWord* GenCollectedHeap::mem_allocate(size_t size, 
  2.                                          bool is_large_noref, 
  3.                                          bool is_tlab, 
  4.                                          bool* gc_overhead_limit_was_exceeded) { 
  5.   return collector_policy()->mem_allocate_work(size, 
  6.                                                is_tlab, 
  7.                                                gc_overhead_limit_was_exceeded); 

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

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

  1. HeapWord* GenCollectorPolicy::mem_allocate_work(size_t size, 
  2.                                         bool is_tlab, 
  3.                                         bool* gc_overhead_limit_was_exceeded) { 
  4.   GenCollectedHeap *gch = GenCollectedHeap::heap(); 
  5.   //... 
  6.   // Loop until the allocation is satisified, 
  7.   // or unsatisfied after GC. 
  8.   for (int try_count = 1/* return or throw */; try_count += 1) {  

对于占用空间比较大的对象,如果经常放在新生代,那么剩余的内存空间就会非常紧张,将可能会导致新生代内存垃圾回收的频繁触发。故若对象的大小超过一定值,那么就不应该分配在新生代。

  1. //...紧接上面部分 
  2. dleMark hm; // discard any handles allocated in each iteration 
  3.  
  4.  // First allocation attempt is lock-free. 
  5.  Generation *gen0 = gch->get_gen(0); 
  6.  
  7.  if (gen0->should_allocate(size, is_tlab)) { 
  8.    result = gen0->par_allocate(size, is_tlab); 
  9.    if (result != NULL) { 
  10.      assert(gch->is_in_reserved(result), "result not in heap"); return result; 
  11.    } 
  12.  } 

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

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

  1. unsigned int gc_count_before;  // read inside the Heap_lock locked region 
  2.     { 
  3.       MutexLocker ml(Heap_lock); 
  4.      //... 
  5.       bool first_only = ! should_try_older_generation_allocation(size); 
  6.  
  7.       result = gch->attempt_allocation(size, is_tlab, first_only); 
  8.       if (result != NULL) { 
  9.         assert(gch->is_in_reserved(result), "result not in heap"); return result; 
  10.       } 

其中should_try_older_generation_allocation()如下:

  1. bool GenCollectorPolicy::should_try_older_generation_allocation( 
  2.         size_t word_size) const { 
  3.   GenCollectedHeap* gch = GenCollectedHeap::heap(); 
  4.   size_t gen0_capacity = gch->get_gen(0)->capacity_before_gc(); 
  5.   return    (word_size > heap_word_size(gen0_capacity)) 
  6.          || GC_locker::is_active_and_needs_gc() 
  7.          || gch->incremental_collection_failed(); 

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

attempt_allocation()实现如下:

  1. HeapWord* GenCollectedHeap::attempt_allocation(size_t size, 
  2.                                                bool is_tlab, 
  3.                                                bool first_only) { 
  4.   HeapWord* res; 
  5.   for (int i = 0; i < _n_gens; i++) { 
  6.     if (_gens[i]->should_allocate(size, is_tlab)) { 
  7.       res = _gens[i]->allocate(size, is_tlab); 
  8.       if (res != NULL) return res; 
  9.       else if (first_only) break
  10.     } 
  11.   } 
  12.   // Otherwise... 
  13.   return NULL; 

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

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

  1. if (GC_locker::is_active_and_needs_gc()) { 
  2.         if (is_tlab) { 
  3.           return NULL;  // Caller will retry allocating individual object 
  4.         } 
  5.         if (!gch->is_maximal_no_gc()) { 
  6.           // Try and expand heap to satisfy request 
  7.           result = expand_heap_and_allocate(size, is_tlab); 
  8.           // result could be null if we are out of space 
  9.           if (result != NULL) { 
  10.             return result; 
  11.           } 
  12.         } 

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

  1. JavaThread* jthr = JavaThread::current(); 
  2.         if (!jthr->in_critical()) { 
  3.           MutexUnlocker mul(Heap_lock); 
  4.           // Wait for JNI critical section to be exited 
  5.           GC_locker::stall_until_clear(); 
  6.           continue
  7.         } 

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

  1. VM_GenCollectForAllocation op(size, 
  2.                                   is_tlab, 
  3.                                   gc_count_before); 
  4.     VMThread::execute(&op); 
  5.     if (op.prologue_succeeded()) { 
  6.       result = op.result(); 
  7.       if (op.gc_locked()) { 
  8.          assert(result == NULL, "must be NULL if gc_locked() is true"); continue// retry and/or stall as necessary 
  9.       } 
否则将等待gc完成,若gc超时则会将gc_overhead_limit_was_exceeded设置为true返回给调用者,并重置超时状态,并对分配的对象进行填充处理

  1. const bool limit_exceeded = size_policy()->gc_overhead_limit_exceeded(); 
  2.   const bool softrefs_clear = all_soft_refs_clear(); 
  3.   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; 
  4.   } 

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




相关栏目:

用户点评