Java虚拟机 对象创建流程、初始化流程

前言

梳理对象创建流程和初始化流程

Java虚拟机运行时数据区

对象创建流程

对象创建

  1. new指令时,定位该指令的参数在常量池中的符号引用
  2. 如果没有,则进行类的加载、连接和初始化。
  3. 虚拟机为新生对象分配内存
  4. 将分配到的内存空间都初始化为零值,不包括对象头,并初始化对象头(哈希码、gc年龄等)
  5. 调用对象方法

1. 类加载流程

深入理解Java虚拟机 第七章 虚拟机类加载时机与过程

2. 对象的内存布局

对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。

1. 对象头

Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针),数组会多1字宽(32位: 4字节)来存储数组长度。

  • Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键。
  • Klass Point是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;

2. 实例数据

该部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容,包括父类继承下来的和子类中定义的。

3. 对齐填充

该部分不是必须的,某些虚拟机要求对象的起始地址必须是8字节的整数倍,即对象大小必须是8字节的整数倍,所以不足时就需要对齐。

4. Mark Word详解

JVM中对象头的方式有以下两种(以32位JVM为例):

// 普通对象
|--------------------------------------------------------------|
|                     Object Header (64 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (32 bits)         |    Klass Word (32 bits) |
|------------------------------------|-------------------------|

// 数组对象
|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|
  • Mark Word
    这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。
    mark word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark word为32位,64位JVM为64位。
    Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间,为了让一个字大小存储更多的信息,JVM将字的最低两个位设置为标记位,不同标记位下的Mark Word示意如下:
|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:0 |lock:01 |     Normal无锁      |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1| lock:01 |     Biased偏向锁    |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30         | lock:00 | Lightweight Locked轻量级锁 |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30 | lock:10 | Heavyweight Locked重量级锁 |
|-------------------------------------------------------|--------------------|
|                                             | lock:11 |    Marked for GC   GC标记|
|-------------------------------------------------------|--------------------|

锁状态

  • lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个mark word表示的含义不同。
  • biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
  • age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
  • identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
  • thread:持有偏向锁的线程ID。
  • epoch:偏向时间戳。
  • ptr_to_lock_record:指向栈中锁记录的指针。
  • ptr_to_heavyweight_monitor:指向管程Monitor的指针。

3. 对象初始化顺序

根据类加载流程,先执行类初始化 clinit,再执行对象初始化init方法。顺序如下:

  1. 父类静态变量
    子类静态变量

  2. 父类静态代码块
    子类静态代码块

  3. 父类方法块
    父类构造函数

  4. 子类方法块
    子类构造函数

3. 对象的访问定位

目前有两种主流方式访问:

  • 句柄
    需要堆单独划出一块内存来作为句柄池,但好处是reference中存储的是稳定的句柄地址,在对象因GC移动时只会改变句柄中的实例指针。
    句柄

  • 直接指针
    好处是速度快
    直接 指针访问

由此知道在GC Roots里,虚拟机栈(栈帧中的本地变量表)中引用的对象指的是什么了。

结语

本文梳理了对象创建的大概流程,对理解虚拟机大有帮助。

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/105968386