前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java对象创建源码分析

Java对象创建源码分析

作者头像
KINGYT
发布2023-03-15 13:54:26
4760
发布2023-03-15 13:54:26
举报

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 卯时卯刻 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档