本文基于openjdk11及hotspot
(想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)
在正式探讨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其实保存的是对象的头部信息,除了头部信息,对象还有数据,对象数据紧跟着头部后面,图示如下:
上图截取了一段程序字节码,红线所框对应了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); // 初始化klass
oop obj = klass->allocate_instance(CHECK); // 分配实例
thread->set_vm_result(obj);
IRT_END
里面主要包含了两个部分:初始化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); // 链接class
bool 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 2
while(is_being_initialized() && !is_reentrant_initialization(self)) {
wait = true;
ol.waitUninterruptibly(CHECK);
}
// Step 3
if (is_being_initialized() && is_reentrant_initialization(self)) {
DTRACE_CLASSINIT_PROBE_WAIT(recursive, -1, wait);
return;
}
// Step 4
if (is_initialized()) {
DTRACE_CLASSINIT_PROBE_WAIT(concurrent, -1, wait);
return;
}
// Step 5
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) {
// Out of memory: can't create detailed error message
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
} else {
jio_snprintf(message, msglen, "%s%s", desc, className);
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
}
}
// Step 6
set_init_state(being_initialized);
set_init_thread(self);
}
// Step 7
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;
// Locks object, set state, and notify all waiting threads
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);
// 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 9
if (!HAS_PENDING_EXCEPTION) {
set_initialization_state_and_notify(fully_initialized, CHECK);
{
debug_only(vtable().verify(tty, true);)
}
}
else {
// Step 10 and 11
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);
}
// 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* 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和itable
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的内容有空新开一章,本文就不描述具体细节了。
这段初始化klass步骤在JVM规范中有详细描述,假设当前类(接口)为C,它持有一个独有的初始化锁LC
上文为JVM11规范中的步骤,实际中可以看到hotspot在实现时和规范所写略有偏差,但基本差不多。
// 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;
}
很容易可以看到,此处的主流程分为两个部分,内存分配和初始化。
直接打开代码,如下:
// 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;
}
// 将分配的空间数据全部清0
if (ZeroTLAB) {
Copy::zero_to_words(mem, allocation._allocated_tlab_size);
}
// 将mem位置分配word_size大小给obj
tlab.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中,对象的分配分为了两种形式:大对象分配、普通分配。由于代码比较长,简单描述大对象的分配过程如下:
接下来的普通分配过程较为复杂,本文就不再深入探究了。
代码如下
// 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()方法就是设置对象的头部数据。
由于平时几乎很少用到finalize(),且内部逻辑比较复杂,因此本文暂时不探究finalize的注册机制。