歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 源碼分析:Java對象的內存分配

源碼分析:Java對象的內存分配

日期:2017/3/1 9:20:45   编辑:Linux編程

Java對象的分配,根據其過程,將其分為快速分配和慢速分配兩種形式,其中快速分配使用無鎖的指針碰撞技術在新生代的Eden區上進行分配,而慢速分配根據堆的實現方式、GC的實現方式、代的實現方式不同而具有不同的分配調用層次。
下面就以bytecodeInterpreter解釋器對於new指令的解釋出發,分析實例對象的內存分配過程:

 一、快速分配

  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.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)字段 。

  以上內容就是堆實現無關的慢速分配過程,其流程圖如下:

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2015-11/125246p2.htm

Copyright © Linux教程網 All Rights Reserved