漫谈Java对象

本文详细阐述了在Hot-Spot虚拟机中,对象从关键字触发到内存分配、零值初始化、构造函数执行的创建过程,以及对象在内存中的布局,包括对象头、实例数据和对齐填充。同时讨论了线程安全的保证策略,如同步机制和TLAB。最后,介绍了对象访问定位的两种方式:句柄和直接指针。
摘要由CSDN通过智能技术生成

接下来我们来讨论下对象在虚拟机中分配、布局和访问的过程。因为不同虚拟机的实现不同,下文我们以Hot-Spot虚拟机为例。

对象的创建

对象的创建总体过程可以概括为 关键字 -> 类型检查 -> 内存分配 -> 零值 -> 构造函数。

关键字触发

当JVM遇到一条字节码new指令的时候,将触发对象创建的流程。

类型检查

首先JVM会检查该指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和初始化。

内存分配

两种分配方法

JVM的内存分配算法与JVM的GC算法息息相关。当JVM的内存时规整的,此时JVM只需要将内存指针向空闲方向挪动即可,这种方法被称为“指针碰撞”。但如果内存是不规整的,那么JVM就需要维护一个列表,来记录哪些部分是可用的,此方法叫做“空闲列表”。

分配的线程安全

仅考虑如何分配内存是不够的,因为在内存分配中也会涉及到线程安全的问题。在并发情况下,当线程A向对象A分配内存,此时A的指针还没来得及修改,线程B直接将对象B创建到了该位置。

有两种办法来保证线程安全:第一种是采用同步机制,利用CAS(compare and swap)操作来保证操作的原子性,这里拓展一下通常CPU支持CAS操作,但是底层也是通过lock指令实现的。另一种是现在TLAB中分配,当TLAB不足时再同步锁定。

赋零值

实例数据

当内存分配完后,虚拟机需要将分配到的值都初始化为零值,这一步操作的位置同内存分配的位置相关,可能在TLAB中进行。这一步的意义在于保证了对象中的实例字段在java代码中可以不赋初始值就可以直接使用。因此,实例代码块总是在实例对象赋值之后执行。 

public class MyObject {
    int i;
    int j = 1;

    {
        System.out.println("init block start:" +i +" | "+j);
        j = 2;
        System.out.println("init block end:" +i +" | "+j);
    }

    public MyObject(int i, int j) {
        this.i = i;
        this.j = j;
    }

}
public class InitTest {
    public static void main(String[] args) {
        MyObject myObject = new MyObject(10, 20);
        System.out.println("Init finished: "+ myObject.i +" | "+ myObject.j);
    }
    /**
     * Output:
     * init block start:0 | 1
     * init block end:0 | 2
     * Init finished: 10 | 20
     */
}

对象头

接下来JVM还需要对此对象进行设置,例如对象是哪个类、如何找到类的元数据、对象的哈希码、对象的GC分代年龄和是否被用作锁等。(Mark Word + Class Pointer)

构造函数

上面的操作都完成后从JVM的角度看一个对象已经创建完成了,但是从java程序的角度来看对象的创建才刚开始——构造函数。在构造函数中,会执行<init>指令,按照程序员的意愿对对象进行初始化。至此一个完成的对象才算是创建完成了。

对象的内存布局

一个对象在内存中通常由三个部分组成:对象头、示例数据和对齐填充。

对象头

Mark Word

Mark Wrod中保存了对象的状态信息等,具体如下表:

存储内容标志位状态
对象哈希码、对象分代年龄01未锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10重量级锁定
空,不需要记录11GC标记
偏向线程ID、偏向时间戳、对象分代年龄01可偏向

Class Pointer

指向类型元数据的指针。

实例数据

即我们在代码中定义的成员变量,包括父类继承的部分。

对齐填充

Hot-Spot要求对象的大小必须是8字节的整数倍,因此需要对齐填充。

对象的访问定位

Java程序通过栈上的reference数据来访问和操作堆上的具体对象。常见的有下面的两种方式。

句柄

句柄下的访问可以抽象成如下过程:

reference -> 句柄池 -> 实例数据+对象类型数据。

该模式下,reference需要句柄池来做“代理”,因此reference不需要跟踪因GC而引起的数据地址变化,句柄池会做更新。但是因为多了一层,导致访问速度较慢

直接指针

在这种模式下reference直接标记对象的内存地址,好处是速度快。但是需要对reference进行维护更新

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值