Java对象创建过程

前言

在Java开发中,我们经常使用new关键字来创建对象,但你是否想过,当执行Person person = new Person()这行代码时,JVM底层究竟发生了什么?让我们看看对象是怎么被创建的。

对象创建的六个核心步骤

1. 类加载检查

当JVM执行引擎遇到new指令时,首先会进行类加载检查:

Person person = new Person();

JVM会执行以下检查:

  • 在常量池中定位到Person类的符号引用
  • 检查Person类是否已经被加载
  • 检查Person类是否已经被解析
  • 检查Person类是否已经被初始化

如果任何一个步骤没有完成,JVM会先执行相应的类加载过程。

2. 分配内存空间

类加载检查通过后,JVM开始为新对象分配内存。对象所需的内存大小在类加载完成后就已经确定。

2.1 两种分配方式

方式一:指针碰撞

定义:假设Java堆中内存是绝对规整的,所有使用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针为分界点的指示器,分配内存就是把那个指针向空闲空间方向挪动一段与对象大小相等的距离。

方式二:空闲列表

定义:假设Java堆中内存并不是规整的,已被使用的内存和空闲的内存相互交错,虚拟机就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

2.2 选择依据

if (垃圾收集器支持压缩整理) {
    使用指针碰撞方式();
} else {
    使用空闲列表方式();
}

3. 并发安全处理

对象创建是高频操作,必须保证线程安全。JVM提供两种解决方案:

3.1 CAS + 失败重试

public Object allocateMemory(int size) {
    do {
        Object current = heapPointer.get();
        Object next = current + size;
        if (heapPointer.compareAndSet(current, next)) {
            return current;
        }
        // CAS失败,重试
    } while (true);
}

3.2 TLAB

每个线程在Java堆中预先分配一块私有内存区域,避免多线程竞争。

TLAB的优势:

  • 减少线程间同步开销
  • 提高内存分配效率
  • 支持快速的对象分配

4. 内存初始化

public class Person {
    private String name;    // 初始化为null
    private int age;        // 初始化为0
    private boolean active; // 初始化为false
}

JVM将分配的内存空间(除对象头外)全部初始化为零值,这确保了实例字段在未显式赋值时也有确定的初始值。

各类型的零值:

数据类型零值
booleanfalse
byte(byte)0
short(short)0
int0
long0L
float0.0f
double0.0d
char'\u0000'
引用类型null

5. 设置对象头

对象头是JVM管理对象的关键数据结构,包含两部分:

5.1 Mark Word

在64位JVM中,Mark Word占用8字节,存储: 对象哈希码、分代年龄、锁标志位、线程ID、时间戳和偏向锁标志。

Mark Word在不同锁状态下的存储内容:

锁状态25bit31bit1bit4bit1bit2bit
无锁状态unusedhashcodeunused分代年龄001
偏向锁ThreadID(54bit)Epoch(2bit)unused分代年龄101
轻量级锁指向栈中锁记录的指针(62bit)unused分代年龄unused00
重量级锁指向互斥量的指针(62bit)unused分代年龄unused10
GC标记CMS过程用的标记信息(62bit)unused分代年龄unused11

5.2 类型指针

类型指针指向对象的类元数据,JVM通过这个指针确定对象是哪个类的实例。在64位JVM中,类型指针通常占用8字节,但开启压缩指针后可压缩至4字节。

5.3 对象头示例

普通对象的对象头结构:

public class Person {
    private String name;
    private int age;
}

对于上述Person对象,其对象头包含:

组成部分大小(64位JVM)内容描述
Mark Word8字节哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
类型指针4字节(压缩)/8字节指向Person.class的类元数据信息
对齐填充0-7字节确保对象大小为8字节的倍数

数组对象的对象头结构:        

数组对象除了Mark Word和类型指针外,还有额外的4字节存储数组长度:

int[] array = new int[10];
组成部分大小(64位JVM)内容描述
Mark Word8字节对象标记信息
类型指针4字节(压缩)/8字节指向int[]的类元数据
数组长度4字节存储数组的长度(10)
对齐填充根据需要保证对象大小对齐

6. 执行构造函数

public class Person {
    private String name;
    private int age;
    
    // 编译器生成的<init>方法
    public Person(String name, int age) {
        super();           // 调用父类构造器
        this.name = name;  // 实例字段初始化
        this.age = age;    // 实例字段初始化
        // 构造函数体逻辑
    }
}

构造函数执行的详细过程:

  1. 隐式调用父类构造器:如果没有显式调用super(),编译器会自动添加super()调用
  2. 实例字段初始化:按照在类中声明的顺序执行字段初始化
  3. 执行构造函数体:执行构造函数中的自定义逻辑

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值