Java对象创建源码分析

2023-03-15 13:54:26 浏览数 (3)

本文将从源码角度分析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。

0 人点赞