本文基于openjdk11及hotspot
Java对象模型: OOP-Klass模型 在正式探讨JVM对象的创建前,先简单地介绍一下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,数据结构如下
1 2 3 4 5 6 7 8 9 class instanceOopDesc : public oopDesc { private : volatile markOop _mark; union _metadata { Klass* _klass; narrowKlass _compressed_klass; } _metadata; };
其中_metadata指向该对象的InstanceKlass,而_mark中则存储了对象运行时的状态数据,数据结构如下(图中为32位的情况下的数据,64位也大同小异)
1 2 3 4 5 6 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分代年龄、锁等状态信息,如下:
1 2 3 4 5 6 hash: 哈希码 age: 分代年龄 biased_lock: 偏向锁标识位 lock: 锁状态标识位 JavaThread*: 持有偏向锁的线程ID epoch: 偏向时间戳
instanceOopDesc其实保存的是对象的头部信息,除了头部信息,对象还有数据,对象数据紧跟着头部后面,图示如下:
1. 入口
上图截取了一段程序字节码,红线所框对应了Java中new操作的字节码,Java中的new操作对应了字节码的三个操作,本文主要讲述第一个操作(new)。字节码中new操作对应JVM中的InterpreterRuntime::_new,代码如下,
1 2 3 4 5 6 7 8 9 10 11 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); klass->initialize (CHECK); oop obj = klass->allocate_instance (CHECK); thread->set_vm_result (obj); IRT_END
里面主要包含了两个部分:初始化klass和分配实例
2. 初始化klass 1 2 3 4 5 6 7 8 void InstanceKlass::initialize (TRAPS) { if (this ->should_be_initialized ()) { initialize_impl (CHECK); } else { assert (is_initialized (), "sanity check" ); } }
在这里我们继续看initialize_impl()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 void InstanceKlass::initialize_impl (TRAPS) { HandleMark hm (THREAD) ; link_class (CHECK); bool wait = false ; { Handle h_init_lock (THREAD, init_lock()) ; ObjectLocker ol (h_init_lock, THREAD, h_init_lock() != NULL ) ; Thread *self = THREAD; while (is_being_initialized () && !is_reentrant_initialization (self)) { wait = true ; ol.waitUninterruptibly (CHECK); } if (is_being_initialized () && is_reentrant_initialization (self)) { DTRACE_CLASSINIT_PROBE_WAIT (recursive, -1 , wait); return ; } if (is_initialized ()) { DTRACE_CLASSINIT_PROBE_WAIT (concurrent, -1 , wait); return ; } if (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) { THROW_MSG (vmSymbols::java_lang_NoClassDefFoundError (), className); } else { jio_snprintf (message, msglen, "%s%s" , desc, className); THROW_MSG (vmSymbols::java_lang_NoClassDefFoundError (), message); } } set_init_state (being_initialized); set_init_thread (self); } if (!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; set_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); { 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); } if (!HAS_PENDING_EXCEPTION) { set_initialization_state_and_notify (fully_initialized, CHECK); { debug_only (vtable ().verify (tty, true );) } } else { Handle 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 链接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 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); ClassLoaderData * 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. 分配实例 1 2 3 4 5 6 7 8 9 10 11 12 13 instanceOop InstanceKlass::allocate_instance (TRAPS) { bool has_finalizer_flag = has_finalizer (); 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 堆空间分配对象 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 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 内存分配 直接打开代码,如下:
1 2 3 4 5 6 7 8 9 10 11 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内分配的过程,
1 2 3 4 5 6 7 8 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的存储信息以及分配空间)
1 2 3 4 5 6 7 8 9 10 11 inline HeapWord* ThreadLocalAllocBuffer::allocate (size_t size) { invariants (); HeapWord* obj = top (); if (pointer_delta (end (), obj) >= size) { set_top (obj + size); invariants (); return obj; } return NULL ; }
接下来我们来看看TLAB内的慢分配,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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; } } if (tlab.free () > tlab.refill_waste_limit ()) { tlab.record_slow_allocation (_word_size); return NULL ; } size_t new_tlab_size = tlab.compute_size (_word_size); tlab.clear_before_allocation (); if (new_tlab_size == 0 ) { return NULL ; } 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 ; } if (ZeroTLAB) { Copy::zero_to_words (mem, allocation._allocated_tlab_size); } tlab.fill (mem, mem + _word_size, allocation._allocated_tlab_size); return mem; }
b. TLAB外分配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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为例,看一看分配的过程。
1 2 3 4 5 6 7 8 9 10 11 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 初始化对象 代码如下
1 2 3 4 5 oop ObjAllocator::initialize (HeapWord* mem) const { mem_clear (mem); return finish (mem); }
其中mem_clear()
方法比较简单,就是将对象除头部以外的数据全部置为0,代码如下,
1 2 3 4 5 6 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()
函数,
1 2 3 4 5 6 7 8 9 10 11 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对象分配的整体流程大致如下,
参考 [1] The Java® Virtual Machine Specification Java SE 11 Edition