目录
  1. Java对象模型: OOP-Klass模型
  2. 对象创建过程
    1. 1. 入口
    2. 2. 初始化klass
      1. 2.1 链接
      2. 2.2 初始化过程
    3. 3. 分配实例
      1. 3.1 堆空间分配对象
        1. 3.1.1 内存分配
          1. a. TLAB内分配
          2. b. TLAB外分配
        2. 3.1.2 初始化对象
      2. 3.2 注册finalize()方法
    4. 4. 整体流程
  3. 参考
Java对象分配原理

本文基于openjdk11及hotspot

Java对象模型: OOP-Klass模型

在正式探讨JVM对象的创建前,先简单地介绍一下hotspot中实现的Java的对象模型。在JVM中,并没有直接将Java对象映射成C++对象,而是采用了oop-klass模型,主要是不希望每个对象中都包含有一份虚函数表,其中:

  1. OOP(Ordinary Object Point),表示对象的实例信息
  2. 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其实保存的是对象的头部信息,除了头部信息,对象还有数据,对象数据紧跟着头部后面,图示如下:

image-20190628015456829

1. 入口

image-20190628025924528

上图截取了一段程序字节码,红线所框对应了Java中new操作的字节码,Java中的new操作对应了字节码的三个操作,本文主要讲述第一个操作(new)。字节码中new操作对应JVM中的InterpreterRuntime::_new,代码如下,

1
2
3
4
5
6
7
8
9
10
11
// 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和分配实例

2. 初始化klass

1
2
3
4
5
6
7
8
// 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()方法

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
// 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);
}

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
// 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和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链接的过程就是这样,主要步骤总结如下:

  1. 链接父类和实现的接口
  2. 重写类
  3. 初始化vtable和itable
  4. 将类的状态标记为已链接

关于重写类和初始化vtable、itable的内容有空新开一章,本文就不描述具体细节了。

2.2 初始化过程

这段初始化klass步骤在JVM规范中有详细描述,假设当前类(接口)为C,它持有一个独有的初始化锁LC

  1. 同步锁LC,防止并发导致多次初始化
  2. 如果有其他线程正在初始化C,就释放LC并阻塞当前线程直到那个线程完成初始化
  3. 如果是执行初始化的是当前线程,则表明是递归请求,释放LC并正常完成初始化
  4. 如果C已经被初始化了,则释放LC并正常完成初始化
  5. 如果C的对象处于一个错误状态,则释放LC并抛出NoClassDefFoundError异常
  6. 记录C正在被当前线程初始化并释放LC,初始化类中所有final static字段
  7. 如果C是一个类,初始化其父类和接口
  8. 判断C是否打开断言
  9. 执行类(接口)的初始化方法
  10. 标记C已经完全初始化,并唤醒所有的等待线程
  11. 如果初始化失败,则抛出异常,并将C标记为错误,同时唤醒所有的等待线程

上文为JVM11规范中的步骤,实际中可以看到hotspot在实现时和规范所写略有偏差,但基本差不多。

3. 分配实例

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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 堆空间分配对象

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 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 内存分配

直接打开代码,如下:

1
2
3
4
5
6
7
8
9
10
11
// 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内分配的过程,

1
2
3
4
5
6
7
8
// 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的存储信息以及分配空间)

1
2
3
4
5
6
7
8
9
10
11
// 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内的慢分配,

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
// 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外分配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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为例,看一看分配的过程。

1
2
3
4
5
6
7
8
9
10
11
// 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中,对象的分配分为了两种形式:大对象分配、普通分配。由于代码比较长,简单描述大对象的分配过程如下:

  1. 检查是否需要GC,如需要则触发GC,因为大对象消耗堆的速度非常快
  2. 计算大对象需要占据多少区块,尝试分配连续的空闲区块
  3. 如果没有足够的连续空间,找到一块包含空闲和不可用的连续区块,尝试扩展
  4. 尝试GC,如果失败达到阈值则分配失败,进行下一步的普通分配

接下来的普通分配过程较为复杂,本文就不再深入探究了。

3.1.2 初始化对象

代码如下

1
2
3
4
5
// hotspot/share/gc/shared/memAllocator.cpp
oop ObjAllocator::initialize(HeapWord* mem) const {
mem_clear(mem);
return finish(mem);
}

其中mem_clear()方法比较简单,就是将对象除头部以外的数据全部置为0,代码如下,

1
2
3
4
5
6
// 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()函数,

1
2
3
4
5
6
7
8
9
10
11
// 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对象分配的整体流程大致如下,

image-20190628025235263

参考

[1] The Java® Virtual Machine Specification Java SE 11 Edition

文章作者: 谷河
文章链接: https://www.lyytaw.com/java/Java%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E5%8E%9F%E7%90%86/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 谷河|BLOG