Java对象分配原理
Java對象模型: OOP-Klass模型
在正式探討JVM對象的創建前,先簡單地介紹一下hotspot中實現的Java的對象模型。在JVM中,并沒有直接將Java對象映射成C++對象,而是采用了oop-klass模型,主要是不希望每個對象中都包含有一份虛函數表,其中:
簡單地說,一個Java類在JVM中被拆分為了兩個部分:數據和描述信息,分別對應OOP和Klass。
在具體的JVM源碼中,當加載一個Class時,會創建一個InstanceKlass對象,實例化的對象則對應InstanceOopDesc,其中InstanceKlass存放在元空間,InstanceOopDesc存放在堆中。
對象創建過程
首先先來看InstanceOopDesc的數據結構,InstanceOopDesc繼承了OopDesc,數據結構如下
// 此處為了方便閱讀,改寫了一下代碼 class instanceOopDesc : public oopDesc {private:volatile markOop _mark;union _metadata {Klass* _klass;narrowKlass _compressed_klass;} _metadata; };其中_metadata指向該對象的InstanceKlass,而_mark中則存儲了對象運行時的狀態數據,數據結構如下(圖中為32位的情況下的數據,64位也大同小異)
32 bits: -------- hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object) JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) size:32 ------------------------------------------>| (CMS free block) PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)每一行都代表了一種情況,描述了哈希碼、GC分代年齡、鎖等狀態信息,如下:
hash: 哈希碼 age: 分代年齡 biased_lock: 偏向鎖標識位 lock: 鎖狀態標識位 JavaThread*: 持有偏向鎖的線程ID epoch: 偏向時間戳instanceOopDesc其實保存的是對象的頭部信息,除了頭部信息,對象還有數據,對象數據緊跟著頭部后面,圖示如下:
?
1. 入口
?
上圖截取了一段程序字節碼,紅線所框對應了Java中new操作的字節碼,Java中的new操作對應了字節碼的三個操作,本文主要講述第一個操作(new)。字節碼中new操作對應JVM中的InterpreterRuntime::_new,代碼如下,
// hotspot/share/interpreter/interpreterRuntime.cpp IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))Klass* k = pool->klass_at(index, CHECK);InstanceKlass* klass = InstanceKlass::cast(k);klass->check_valid_for_instantiation(true, CHECK); // 校驗:接口/抽象類/Class不能實例化klass->initialize(CHECK); // 初始化klassoop obj = klass->allocate_instance(CHECK); // 分配實例thread->set_vm_result(obj); IRT_END 里面主要包含了兩個部分:初始化klass和分配實例2. 初始化klass
// hotspot/share/oops/instanceKlass.cpp void InstanceKlass::initialize(TRAPS) {if (this->should_be_initialized()) {initialize_impl(CHECK);} else {assert(is_initialized(), "sanity check");} }在這里我們繼續看initialize_impl()方法
// hotspot/share/oops/instanceKlass.cpp void InstanceKlass::initialize_impl(TRAPS) {HandleMark hm(THREAD);link_class(CHECK); // 鏈接classbool wait = false;// Step 1{Handle h_init_lock(THREAD, init_lock());ObjectLocker ol(h_init_lock, THREAD, h_init_lock() != NULL);Thread *self = THREAD;// Step 2while(is_being_initialized() && !is_reentrant_initialization(self)) {wait = true;ol.waitUninterruptibly(CHECK);}// Step 3if (is_being_initialized() && is_reentrant_initialization(self)) {DTRACE_CLASSINIT_PROBE_WAIT(recursive, -1, wait);return;}// Step 4if (is_initialized()) {DTRACE_CLASSINIT_PROBE_WAIT(concurrent, -1, wait);return;}// Step 5if (is_in_error_state()) {DTRACE_CLASSINIT_PROBE_WAIT(erroneous, -1, wait);ResourceMark rm(THREAD);const char* desc = "Could not initialize class ";const char* className = external_name();size_t msglen = strlen(desc) + strlen(className) + 1;char* message = NEW_RESOURCE_ARRAY(char, msglen);if (NULL == message) {// Out of memory: can't create detailed error messageTHROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);} else {jio_snprintf(message, msglen, "%s%s", desc, className);THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);}}// Step 6set_init_state(being_initialized);set_init_thread(self);}// Step 7if (!is_interface()) {Klass* super_klass = super();if (super_klass != NULL && super_klass->should_be_initialized()) {super_klass->initialize(THREAD);}if (!HAS_PENDING_EXCEPTION && has_nonstatic_concrete_methods()) {initialize_super_interfaces(THREAD);}if (HAS_PENDING_EXCEPTION) {Handle e(THREAD, PENDING_EXCEPTION);CLEAR_PENDING_EXCEPTION;{EXCEPTION_MARK;// Locks object, set state, and notify all waiting threadsset_initialization_state_and_notify(initialization_error, THREAD);CLEAR_PENDING_EXCEPTION;}DTRACE_CLASSINIT_PROBE_WAIT(super__failed, -1, wait);THROW_OOP(e());}}AOTLoader::load_for_klass(this, THREAD);// Step 8{assert(THREAD->is_Java_thread(), "non-JavaThread in initialize_impl");JavaThread* jt = (JavaThread*)THREAD;DTRACE_CLASSINIT_PROBE_WAIT(clinit, -1, wait);PerfClassTraceTime timer(ClassLoader::perf_class_init_time(),ClassLoader::perf_class_init_selftime(),ClassLoader::perf_classes_inited(),jt->get_thread_stat()->perf_recursion_counts_addr(),jt->get_thread_stat()->perf_timers_addr(),PerfClassTraceTime::CLASS_CLINIT);call_class_initializer(THREAD);}// Step 9if (!HAS_PENDING_EXCEPTION) {set_initialization_state_and_notify(fully_initialized, CHECK);{debug_only(vtable().verify(tty, true);)}}else {// Step 10 and 11Handle e(THREAD, PENDING_EXCEPTION);CLEAR_PENDING_EXCEPTION;JvmtiExport::clear_detected_exception((JavaThread*)THREAD);{EXCEPTION_MARK;set_initialization_state_and_notify(initialization_error, THREAD);CLEAR_PENDING_EXCEPTION;JvmtiExport::clear_detected_exception((JavaThread*)THREAD);}DTRACE_CLASSINIT_PROBE_WAIT(error, -1, wait);if (e->is_a(SystemDictionary::Error_klass())) {THROW_OOP(e());} else {JavaCallArguments args(e);THROW_ARG(vmSymbols::java_lang_ExceptionInInitializerError(),vmSymbols::throwable_void_signature(),&args);}}DTRACE_CLASSINIT_PROBE_WAIT(end, -1, wait); }2.1 鏈接
// hotspot/share/oops/instanceKlass.cpp bool InstanceKlass::link_class_impl(bool throw_verifyerror, TRAPS) {if (is_linked()) {return true;}assert(THREAD->is_Java_thread(), "non-JavaThread in link_class_impl");JavaThread* jt = (JavaThread*)THREAD;// 先鏈接父類Klass* super_klass = super();if (super_klass != NULL) {if (super_klass->is_interface()) {ResourceMark rm(THREAD);Exceptions::fthrow(THREAD_AND_LOCATION,vmSymbols::java_lang_IncompatibleClassChangeError(),"class %s has interface %s as super class",external_name(),super_klass->external_name());return false;}InstanceKlass* ik_super = InstanceKlass::cast(super_klass);ik_super->link_class_impl(throw_verifyerror, CHECK_false);}// 鏈接該類的所有借口Array<Klass*>* interfaces = local_interfaces();int num_interfaces = interfaces->length();for (int index = 0; index < num_interfaces; index++) {InstanceKlass* interk = InstanceKlass::cast(interfaces->at(index));interk->link_class_impl(throw_verifyerror, CHECK_false);}if (is_linked()) {return true;}// 驗證 & 重寫{HandleMark hm(THREAD);Handle h_init_lock(THREAD, init_lock());ObjectLocker ol(h_init_lock, THREAD, h_init_lock() != NULL);if (!is_linked()) {if (!is_rewritten()) {{bool verify_ok = verify_code(throw_verifyerror, THREAD);if (!verify_ok) {return false;}}if (is_linked()) {return true;}// 重寫類rewrite_class(CHECK_false);} else if (is_shared()) {SystemDictionaryShared::check_verification_constraints(this, CHECK_false);}// 重寫完成后鏈接方法link_methods(CHECK_false);// 初始化vtable和itableClassLoaderData * loader_data = class_loader_data();if (!(is_shared() &&loader_data->is_the_null_class_loader_data())) {ResourceMark rm(THREAD);vtable().initialize_vtable(true, CHECK_false);itable().initialize_itable(true, CHECK_false);}// 將類的狀態標記為已鏈接set_init_state(linked);if (JvmtiExport::should_post_class_prepare()) {Thread *thread = THREAD;assert(thread->is_Java_thread(), "thread->is_Java_thread()");JvmtiExport::post_class_prepare((JavaThread *) thread, this);}}}return true; }class鏈接的過程就是這樣,主要步驟總結如下:
關于重寫類和初始化vtable、itable的內容有空新開一章,本文就不描述具體細節了。
2.2 初始化過程
這段初始化klass步驟在JVM規范中有詳細描述,假設當前類(接口)為C,它持有一個獨有的初始化鎖LC
上文為JVM11規范中的步驟,實際中可以看到hotspot在實現時和規范所寫略有偏差,但基本差不多。
3. 分配實例
// hotspot/share/oops/instanceKlass.cpp instanceOop InstanceKlass::allocate_instance(TRAPS) {bool has_finalizer_flag = has_finalizer(); // 是否存在非空finalize()方法int size = size_helper(); // 類的大小instanceOop i;i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL); // 分配對象if (has_finalizer_flag && !RegisterFinalizersAtInit) {i = register_finalizer(i, CHECK_NULL);}return i; } 在這里我們比較關注的是堆空間分配對象環節,3.1 堆空間分配對象
代碼如下:
// hotspot/share/gc/shared/memAllocator.cpp oop MemAllocator::allocate() const {oop obj = NULL;{Allocation allocation(*this, &obj);HeapWord* mem = mem_allocate(allocation);if (mem != NULL) {obj = initialize(mem);}}return obj; } 很容易可以看到,此處的主流程分為兩個部分,內存分配和初始化。3.1.1 內存分配
直接打開代碼,如下:
// hotspot/share/gc/shared/memAllocator.cpp HeapWord* MemAllocator::mem_allocate(Allocation& allocation) const {if (UseTLAB) {HeapWord* result = allocate_inside_tlab(allocation);if (result != NULL) {return result;}}return allocate_outside_tlab(allocation); } 在這段代碼中,我們可以看到一個很耳熟的東西——TLAB(ThreadLocalAllocBuffer),默認情況下TLAB是打開狀態,而且其對Java性能提升非常顯著。首先,先簡單介紹一下TLAB的概念,因為JVM堆空間是所有線程共享的,因此分配一個對象時會鎖住整個堆,這樣效率就會比較低下。因此JVM在eden區分配了一塊空間作為線程的私有緩沖區,這個緩沖區稱為TLAB。不同線程不共享TLAB,因此在TLAB中分配對象時是無需上鎖的,從而可以快速分配。
在這段代碼中,內存分配劃分為了兩個部分——TLAB內分配和TLAB外分配。
a. TLAB內分配
我們先來看看TLAB內分配的過程,
// hotspot/share/gc/shared/memAllocator.cpp HeapWord* MemAllocator::allocate_inside_tlab(Allocation& allocation) const {HeapWord* mem = _thread->tlab().allocate(_word_size);if (mem != NULL) {return mem;}return allocate_inside_tlab_slow(allocation); }同樣的在TLAB的分配的過程中,也被拆成了兩種情況,一種是直接使用線程現有的TLAB來進行分配,代碼如下,在下面的這段代碼中,我們可以看到TLAB的分配就只是簡單地將top指針向上增加了size大小,并且將原先top的位置分配給了obj,因此分配效率可以說是極速了。(事實上,TLAB就是通過start、top、end等指針標記了TLAB的存儲信息以及分配空間)
// hotspot/share/gc/shared/threadLocalAllocBuffer.inline.hpp inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {invariants(); // 校驗TLAB是否合法HeapWord* obj = top();if (pointer_delta(end(), obj) >= size) {set_top(obj + size);invariants();return obj;}return NULL; }接下來我們來看看TLAB內的慢分配,
// hotspot/share/gc/shared/memAllocator.cpp HeapWord* MemAllocator::allocate_inside_tlab_slow(Allocation& allocation) const {HeapWord* mem = NULL;ThreadLocalAllocBuffer& tlab = _thread->tlab();if (ThreadHeapSampler::enabled()) {tlab.set_back_allocation_end();mem = tlab.allocate(_word_size);if (mem != NULL) {allocation._tlab_end_reset_for_sample = true;return mem;}}// 如果TLAB的剩余空間大于閾值,則保留TLAB,這樣就會進入TLAB外分配。在這里,每次TLAB分配失敗,該TLAB都會調大該閾值,以防線程重復分配同樣大小的對象if (tlab.free() > tlab.refill_waste_limit()) {tlab.record_slow_allocation(_word_size);return NULL;}// 計算一個新的TLAB的大小,公式=min{可用空間,期待空間+對象占據空間,最大TLAB空間}size_t new_tlab_size = tlab.compute_size(_word_size);// 清理原先的TLAB。會將剩余的未使用空間填充進一個假數組,創造EDEN連續的假象,并且將start、end、top等指針全部置為空tlab.clear_before_allocation();if (new_tlab_size == 0) {return NULL;}// 創建一個新的TLAB,空間可能在min_tlab_size到new_tlab_size之間size_t min_tlab_size = ThreadLocalAllocBuffer::compute_min_size(_word_size);mem = _heap->allocate_new_tlab(min_tlab_size, new_tlab_size, &allocation._allocated_tlab_size);if (mem == NULL) {return NULL;}// 將分配的空間數據全部清0if (ZeroTLAB) {Copy::zero_to_words(mem, allocation._allocated_tlab_size);}// 將mem位置分配word_size大小給objtlab.fill(mem, mem + _word_size, allocation._allocated_tlab_size);return mem; }b. TLAB外分配
// hotspot/share/gc/shared/memAllocator.cpp HeapWord* MemAllocator::allocate_outside_tlab(Allocation& allocation) const {allocation._allocated_outside_tlab = true;HeapWord* mem = _heap->mem_allocate(_word_size, &allocation._overhead_limit_exceeded);if (mem == NULL) {return mem;}NOT_PRODUCT(_heap->check_for_non_bad_heap_word_value(mem, _word_size));size_t size_in_bytes = _word_size * HeapWordSize;_thread->incr_allocated_bytes(size_in_bytes);return mem; } 這里的核心關注點只有一個——堆內存分配,此處以openjdk11的默認GC——G1為例,看一看分配的過程。 // hotspot/share/gc/g1/g1CollectedHeap.cpp HeapWord* G1CollectedHeap::mem_allocate(size_t word_size,bool* gc_overhead_limit_was_exceeded) {assert_heap_not_locked_and_not_at_safepoint();if (is_humongous(word_size)) {return attempt_allocation_humongous(word_size);}size_t dummy = 0;return attempt_allocation(word_size, word_size, &dummy); }在G1中,對象的分配分為了兩種形式:大對象分配、普通分配。由于代碼比較長,簡單描述大對象的分配過程如下:
接下來的普通分配過程較為復雜,本文就不再深入探究了。
3.1.2 初始化對象
代碼如下
// hotspot/share/gc/shared/memAllocator.cpp oop ObjAllocator::initialize(HeapWord* mem) const {mem_clear(mem);return finish(mem); }其中mem_clear()方法比較簡單,就是將對象除頭部以外的數據全部置為0,代碼如下, // hotspot/share/gc/shared/memAllocator.cpp void MemAllocator::mem_clear(HeapWord* mem) const {const size_t hs = oopDesc::header_size();oopDesc::set_klass_gap(mem, 0);Copy::fill_to_aligned_words(mem + hs, _word_size - hs); }接下來看看finish()函數,
// hotspot/share/gc/shared/memAllocator.cpp oop MemAllocator::finish(HeapWord* mem) const {assert(mem != NULL, "NULL object pointer");if (UseBiasedLocking) {oopDesc::set_mark_raw(mem, _klass->prototype_header());} else {oopDesc::set_mark_raw(mem, markOopDesc::prototype());}oopDesc::release_set_klass(mem, _klass);return oop(mem); }還記得對象頭中有兩個屬性mark和metadata嗎?finish()方法就是設置對象的頭部數據。
3.2 注冊finalize()方法
由于平時幾乎很少用到finalize(),且內部邏輯比較復雜,因此本文暫時不探究finalize的注冊機制。
4. 整體流程
整個JVM對象分配的整體流程大致如下,
總結
以上是生活随笔為你收集整理的Java对象分配原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql函数未定义_未定义的函数,MY
- 下一篇: Java Object类的各个方法