本文将从源码角度分析Java对象是如何被创建的。OpenJDK版本
➜ hg id 76072a077ee1 jdk-11 28
首先来看段Java代码
代码语言:javascript复制public class Hello {
private Hello() {}
static Hello create() {
return new Hello();
}
}
其对应的字节码为
代码语言:javascript复制➜ javac Hello.java
➜ javap -c Hello
Compiled from "Hello.java"
public class Hello {
static Hello create();
Code:
0: new #2 // class Hello
3: dup
4: invokespecial #3 // Method "<init>":()V
7: areturn
}
在上面的create方法中,各字节码的含义是:
new 创建一个Hello对象,并把它放入栈中。
dup 复制一份栈顶的Hello对象,并把它放入栈中。
invokespecial 在栈顶取出一个Hello对象,并调用其<init>方法(默认构造函数)。
areturn 取出栈中剩余的Hello对象,并返回。
因为本文分析的是Java对象的创建过程,所以这里我们只看字节码new,其他字节码不再赘述。
首先来看下字节码new对应的JVM中的代码
代码语言:javascript复制// src/hotspot/cpu/x86/templateTable_x86.cpp
void TemplateTable::_new() {
...
__ get_constant_pool(rarg1);
__ get_unsigned_2_byte_index_at_bcp(rarg2, 1);
call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), rarg1, rarg2);
...
}
该方法其实有大段的汇编代码,但为了方便理解,我们将其都省略了,最后如上面的样子。
由上面代码我们可以看到,该方法最终调用了InterpreterRuntime::_new方法,继续看下这个方法
代码语言:javascript复制// src/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->initialize(CHECK);
...
oop obj = klass->allocate_instance(CHECK);
thread->set_vm_result(obj);
IRT_END
该方法会先从ConstantPool中拿出我们要创建对象所属的类,然后再调用klass->initialize方法确保其初始化完成,最后调用klass->allocate_instance方法真正创建对象。看下该方法
代码语言:javascript复制// src/hotspot/share/oops/instanceKlass.cpp
instanceOop InstanceKlass::allocate_instance(TRAPS) {
...
int size = size_helper(); // Query before forming handle.
instanceOop i;
i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL);
...
return i;
}
在该方法中,首先调用size_helper方法获取这个对象占用内存的大小,然后再调用Universe::heap()->obj_allocate方法在堆上分配一块该大小的内存,最后将其转成instanceOop类型并返回。
在继续看Universe::heap()->obj_allocate方法之前,我们先来看下size_helper返回的值是如何被计算出来的。不过由于其涉及代码太多,我们这里只列些主要部分。
在类加载过程中,会调用下面的方法计算其对象所占内存大小
代码语言:javascript复制// src/hotspot/share/classfile/classFileParser.cpp
void ClassFileParser::layout_fields(ConstantPool* cp,
const FieldAllocationCount* fac,
const ClassAnnotationCollector* parsed_annotations,
FieldLayoutInfo* info,
TRAPS) {
...
int nonstatic_field_size = _super_klass == NULL ? 0 :
_super_klass->nonstatic_field_size();
...
int nonstatic_fields_start = instanceOopDesc::base_offset_in_bytes()
nonstatic_field_size * heapOopSize;
...
int nonstatic_fields_end = align_up(notaligned_nonstatic_fields_end, heapOopSize);
int instance_end = align_up(notaligned_nonstatic_fields_end, wordSize);
...
nonstatic_field_size = nonstatic_field_size
(nonstatic_fields_end - nonstatic_fields_start) / heapOopSize;
int instance_size = align_object_size(instance_end / wordSize);
...
}
该方法最开始先获取super中的非静态字段所占内存大小,之后计算该类的非静态字段的起始偏移位置,计算方法为,对象头instanceOopDesc所占内存大小 super中的非静态字段所占内存大小。
再之后,会遍历该类中的所有非静态字段,为它们依次指定内存位置。非静态字段指定位置完成之后,通过内存对齐,得到nonstatic_fields_end和instance_end的值。
最后,通过以上值计算出nonstatic_field_size和instance_size的大小。
这里的nonstatic_field_size值是给子类用的,这样子类可以算出其nonstatic_fields_start的值。而instance_size的值就是该类创建实例最终占用的内存大小,也就是上面InstanceKlass::allocate_instance方法中,size_helper返回的值(不完全一致)。
综上,一个普通对象内存大小及分布基本上为:对象头 instanceOopDesc 所占内存大小 super中的非静态字段占用内存大小 该类中的非静态字段占内存大小。
我们再看下instanceOopDesc类的结构
代码语言:javascript复制// src/hotspot/share/oops/instanceOop.hpp
class instanceOopDesc : public oopDesc {
...
}
该类继承了oopDesc,我们再看下这个类
代码语言:javascript复制class oopDesc {
...
volatile markOop _mark;
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
...
}
该类中_mark字段所属类型markOop是一个指针,但其并不是用来存放其他对象的地址,而是用来存放具体值,不同的值有不同的意义甚至对应不同的功能,比如Java中的synchronized关键字就是依靠它来实现的。
该类中的_metadata字段是用来标识这个对象所属的类,Java对象就是通过它来获取各种和类有关的信息的。
讲到这里,对于一个普通的Java对象在内存中是什么样子,大家应该都明白了,那我们可以继续上面的Universe::heap()->obj_allocate方法,看下其是如何分配内存的
代码语言:javascript复制// src/hotspot/share/gc/shared/collectedHeap.cpp
oop CollectedHeap::obj_allocate(Klass* klass, int size, TRAPS) {
ObjAllocator allocator(klass, size, THREAD);
return allocator.allocate();
}
继续看下allocator.allocate方法
代码语言:javascript复制// src/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;
}
这里的mem_allocate方法就会为对象真正的分配内存,不过因为这里涉及的细节太多,我们就跳过,只要知道该方法最终返回给我们一块size大小的内存就好。我们重点看下initialize方法
代码语言:javascript复制// src/hotspot/share/gc/shared/memAllocator.cpp
void MemAllocator::mem_clear(HeapWord* mem) const {
...
}
oop MemAllocator::finish(HeapWord* mem) const {
...
if (UseBiasedLocking) {
oopDesc::set_mark_raw(mem, _klass->prototype_header());
} else {
// May be bootstrapping
oopDesc::set_mark_raw(mem, markOopDesc::prototype());
}
...
oopDesc::release_set_klass(mem, _klass);
return oop(mem);
}
oop ObjAllocator::initialize(HeapWord* mem) const {
mem_clear(mem);
return finish(mem);
}
该方法会首先调用mem_clear方法,把这块内存初始化为0,再调用finish方法,初始化对象头oopDesc中的各字段值。
至此,一个对象也就创建完成了。
此时,它在内存中大致的样子是:
对象头oopDesc中的_mark字段根据是否启用偏向锁会被设置成不同的值。
对象头oopDesc中的_metadata字段设置为该对象对应的_klass。
该对象的其他内存区域初始化为0。