首页 > JAVA > 当前页面

JVM创建对象的内存是如何分配的?如何保证线程安全?

2026-06-09 NEW个对象

🚀 JVM创建对象的内存是如何分配的?如何保证线程安全?

📌 核心结论:
Java对象默认在堆中分配内存。当多个线程同时创建对象时,JVM通过CAS、自旋重试、本地线程分配缓冲区(TLAB)等机制保证内存分配过程的线程安全。对象创建过程涉及类加载检查、内存分配、零值初始化、对象头设置以及构造方法执行等多个步骤,是JVM面试中的高频考点。

1️⃣ 问题背景

Java程序运行过程中会不断创建对象。

例如下面一行代码:

User user = new User();

表面上看只是执行了一个new操作。

实际上JVM内部需要完成一系列复杂工作。

  • 检查类是否加载
  • 计算对象大小
  • 申请内存空间
  • 保证并发安全
  • 初始化对象头
  • 执行构造方法

尤其是在高并发场景下,如果数百个线程同时执行new操作,那么对象内存分配过程必须保证线程安全,否则极有可能导致内存覆盖和数据损坏。

⚠️ 面试官通常会从“new一个对象发生了什么”开始,逐步深入到对象内存分配和线程安全机制。

2️⃣ 核心原理

JVM创建对象主要发生在堆内存中。

对象创建过程大致如下:

执行new指令

检查类是否加载

类未加载则执行类加载

计算对象所需空间

在堆中申请内存

初始化对象头

初始化实例变量

执行构造方法

返回对象引用

整个过程中最核心的问题就是:

💡 多个线程同时创建对象时,如何保证不会分配到同一块内存?

这也是JVM内存分配机制需要解决的核心问题。

3️⃣ 数据结构分析

对象在堆中的结构

HotSpot虚拟机中的对象通常由三部分组成。

对象头 Header

实例数据 Instance Data

对齐填充 Padding

对象头(Object Header)

对象头保存对象运行时信息。

  • HashCode
  • GC年龄
  • 锁状态
  • 偏向锁信息
  • 类元数据指针

例如:

new User();

创建后对象头会记录该对象对应的Class信息。

实例数据

实例数据保存真正的成员变量。

class User {
  private Long id;
  private String name;
}

id和name最终都会存储在实例数据区域。

对齐填充

HotSpot要求对象大小必须是8字节整数倍。

如果对象长度不足8字节对齐要求,则自动补齐。

4️⃣ 算法分析

指针碰撞(Bump The Pointer)

当堆内存规整时,JVM采用指针碰撞方式分配对象。

已使用空间
↓↓↓↓↓↓↓↓
###########

分配指针
↓↓
向后移动对象大小

例如对象大小为32字节。

只需将指针向后移动32字节即可完成分配。

空闲列表(Free List)

如果堆内存不连续。

例如CMS垃圾回收器产生大量碎片。

此时JVM采用空闲链表方式寻找可用空间。

空闲块1
空闲块2
空闲块3

找到满足大小要求的区域

然后完成对象分配。

5️⃣ 执行流程

执行new指令

检查Class是否已加载

计算对象大小

TLAB分配?



线程私有区域分配



CAS更新堆指针

零值初始化

对象头初始化

执行构造方法

返回对象引用

零值初始化

JVM会先把所有成员变量赋默认值。

int age = 0;
boolean flag = false;
Object obj = null;

然后再执行构造方法中的赋值逻辑。

6️⃣ 实际案例

案例一:单线程创建对象

User user = new User();

此时对象直接在Eden区分配。

分配完成后返回对象引用地址。

案例二:多线程同时创建对象

Thread-1 → new User()
Thread-2 → new User()
Thread-3 → new User()

如果多个线程同时修改堆顶指针。

可能出现:

❌ 多个对象分配到同一块内存区域

因此JVM必须保证分配过程线程安全。

CAS保证线程安全

JVM内部使用CAS更新堆顶指针。

oldTop = heapTop
newTop = oldTop + objectSize
CAS(oldTop,newTop)

如果CAS失败。

说明其他线程已经修改成功。

当前线程重新尝试即可。

TLAB保证线程安全

JVM真正常用的是TLAB机制。

TLAB全称:

Thread Local Allocation Buffer

即线程本地分配缓冲区。

每个线程在Eden区拥有一小块私有空间。

Eden区

TLAB1 → Thread1
TLAB2 → Thread2
TLAB3 → Thread3

线程优先在自己的TLAB中分配对象。

由于TLAB属于线程私有,因此无需加锁。

💡 大部分对象实际上都是在TLAB中完成分配的。

当TLAB空间不足时,才会退化到CAS方式竞争全局堆空间。

7️⃣ 优缺点分析

方案 优点 缺点
CAS 实现简单 高并发下存在竞争
TLAB 无锁分配 占用部分额外空间
指针碰撞 速度最快 要求内存规整
空闲列表 适应碎片化内存 分配效率较低

8️⃣ 面试常见问题

new一个对象发生了什么?

类加载检查、内存分配、零值初始化、对象头设置、执行构造方法。

对象一定在堆中创建吗?

不一定。

JIT优化后可能发生栈上分配和标量替换。

对象创建如何保证线程安全?

主要依靠CAS和TLAB。

TLAB负责绝大部分对象分配。

为什么TLAB效率高?

因为线程拥有私有缓冲区,无需锁竞争。

什么时候会使用CAS?

TLAB空间不足或者大对象直接分配时。

对象头中存储什么?

HashCode、GC年龄、锁状态、Class指针等信息。

对象创建最核心的线程安全机制是什么?

TLAB + CAS。

TLAB解决绝大多数情况,CAS解决共享区域竞争问题。

9️⃣ 总结

✅ 对象创建本质是在堆中申请内存空间。

✅ 创建过程包括类加载检查、内存分配、初始化对象头和执行构造方法。

✅ 堆内存规整时采用指针碰撞分配。

✅ 堆内存不规整时采用空闲列表分配。

✅ JVM通过CAS保证共享内存分配安全。

✅ JVM通过TLAB实现无锁对象分配。

✅ TLAB是现代JVM对象创建性能优化的核心机制。

🎯 面试一句话总结:

Java对象创建时首先进行类加载检查,然后在堆中申请内存。内存分配采用指针碰撞或空闲列表算法,多线程环境下通过CAS和TLAB保证线程安全,其中TLAB是主流JVM提升对象创建性能的核心机制,大部分对象都会优先在TLAB中完成无锁分配。

相关文章

NEW个对象 NEW个对象
JAVA是世界上最好的语言

推荐文章