Java物件記憶體分配原理及原始碼分析
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)欄位 。
以上內容就是堆實現無關的慢速分配過程,其流程圖如下:
三、堆的分配實現
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實現無關的分配過程,其流程圖歸納如下:
相關文章
- MySQL • 原始碼分析 • 記憶體分配機制MySql原始碼記憶體
- JAVA物件在JVM中記憶體分配Java物件JVM記憶體
- Java記憶體問題 及 LeakCanary 原理分析Java記憶體
- Java 物件記憶體分析Java物件記憶體
- mimalloc記憶體分配程式碼分析記憶體
- java記憶體分配Java記憶體
- Java 記憶體分配策略Java記憶體
- java jvm 記憶體分配JavaJVM記憶體
- 物件的建立與記憶體分配物件記憶體
- Java中物件並不是都在堆上分配記憶體的。Java物件記憶體
- Netty原始碼—六、tiny、small記憶體分配Netty原始碼記憶體
- java基礎-記憶體分配Java記憶體
- java-方法記憶體分配Java記憶體
- Java 堆疊記憶體分配Java記憶體
- Java記憶體管理原理及記憶體區域詳解Java記憶體
- Java是否可以棧上分配物件記憶體? 為什麼?Java物件記憶體
- 深入理解golang:記憶體分配原理Golang記憶體
- Swoole 原始碼分析——記憶體模組之記憶體池原始碼記憶體
- Memcached記憶體管理原始碼分析記憶體原始碼
- 【Java】 記憶體分配全面淺析Java記憶體
- new、delete、記憶體分配 的底層原理delete記憶體
- jvm:記憶體模型、記憶體分配及GC垃圾回收機制JVM記憶體模型GC
- Java物件記憶體模型Java物件記憶體模型
- Java 8 記憶體管理原理解析及記憶體故障排查實踐Java記憶體
- java基礎:記憶體分配機制Java記憶體
- Java記憶體區域與分配策略Java記憶體
- Java記憶體的原型及工作原理深度剖析Java記憶體原型
- ART執行時為新建立物件分配記憶體的過程分析物件記憶體
- JavaScript記憶體分配JavaScript記憶體
- JVM記憶體分配JVM記憶體
- 垃圾收集器與記憶體分配策略_記憶體分配策略記憶體
- Java物件記憶體佈局Java物件記憶體
- JVM記憶體結構、Java記憶體模型和Java物件模型JVM記憶體Java模型物件
- JVM 記憶體模型 記憶體分配,JVM鎖JVM記憶體模型
- JVM基礎:JVM記憶體組成及分配JVM記憶體
- synchronized原理-位元組碼分析、物件記憶體結構、鎖升級過程、Monitorsynchronized物件記憶體
- AQS的原理及原始碼分析AQS原始碼
- Java記憶體分析一Java記憶體