做图赚钱的网站,正规的跨境电商平台有哪些,企业人力资源管理师,asp.net mvc5网站开发之美 pdfJava对象模型: OOP-Klass模型
在正式探讨JVM对象的创建前#xff0c;先简单地介绍一下hotspot中实现的Java的对象模型。在JVM中#xff0c;并没有直接将Java对象映射成C对象#xff0c;而是采用了oop-klass模型#xff0c;主要是不希望每个对象中都包含有一份虚函数表先简单地介绍一下hotspot中实现的Java的对象模型。在JVM中并没有直接将Java对象映射成C对象而是采用了oop-klass模型主要是不希望每个对象中都包含有一份虚函数表其中
OOP(Ordinary Object Point)表示对象的实例信息Klass是Java类的在C中的表示用来描述Java类的信息
简单地说一个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: cant 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);}// 链接该类的所有借口ArrayKlass** 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将类的状态标记为已链接
关于重写类和初始化vtable、itable的内容有空新开一章本文就不描述具体细节了。
2.2 初始化过程
这段初始化klass步骤在JVM规范中有详细描述假设当前类(接口)为C它持有一个独有的初始化锁LC
同步锁LC防止并发导致多次初始化如果有其他线程正在初始化C就释放LC并阻塞当前线程直到那个线程完成初始化如果是执行初始化的是当前线程则表明是递归请求释放LC并正常完成初始化如果C已经被初始化了则释放LC并正常完成初始化如果C的对象处于一个错误状态则释放LC并抛出NoClassDefFoundError异常记录C正在被当前线程初始化并释放LC初始化类中所有final static字段如果C是一个类初始化其父类和接口判断C是否打开断言执行类(接口)的初始化方法标记C已经完全初始化并唤醒所有的等待线程如果初始化失败则抛出异常并将C标记为错误同时唤醒所有的等待线程
上文为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中对象的分配分为了两种形式大对象分配、普通分配。由于代码比较长简单描述大对象的分配过程如下
检查是否需要GC如需要则触发GC因为大对象消耗堆的速度非常快计算大对象需要占据多少区块尝试分配连续的空闲区块如果没有足够的连续空间找到一块包含空闲和使用中的连续区块尝试扩展尝试GC如果失败达到阈值则分配失败进行下一步的普通分配
接下来的普通分配过程较为复杂本文就不再深入探究了。
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对象分配的整体流程大致如下