JVM创建对象的内存是如何分配的?如何保证线程安全?
🚀 JVM创建对象的内存是如何分配的?如何保证线程安全?
Java对象默认在堆中分配内存。当多个线程同时创建对象时,JVM通过CAS、自旋重试、本地线程分配缓冲区(TLAB)等机制保证内存分配过程的线程安全。对象创建过程涉及类加载检查、内存分配、零值初始化、对象头设置以及构造方法执行等多个步骤,是JVM面试中的高频考点。
1️⃣ 问题背景
Java程序运行过程中会不断创建对象。
例如下面一行代码:
表面上看只是执行了一个new操作。
实际上JVM内部需要完成一系列复杂工作。
- 检查类是否加载
- 计算对象大小
- 申请内存空间
- 保证并发安全
- 初始化对象头
- 执行构造方法
尤其是在高并发场景下,如果数百个线程同时执行new操作,那么对象内存分配过程必须保证线程安全,否则极有可能导致内存覆盖和数据损坏。
2️⃣ 核心原理
JVM创建对象主要发生在堆内存中。
对象创建过程大致如下:
↓
检查类是否加载
↓
类未加载则执行类加载
↓
计算对象所需空间
↓
在堆中申请内存
↓
初始化对象头
↓
初始化实例变量
↓
执行构造方法
↓
返回对象引用
整个过程中最核心的问题就是:
这也是JVM内存分配机制需要解决的核心问题。
3️⃣ 数据结构分析
对象在堆中的结构
HotSpot虚拟机中的对象通常由三部分组成。
↓
实例数据 Instance Data
↓
对齐填充 Padding
对象头(Object Header)
对象头保存对象运行时信息。
- HashCode
- GC年龄
- 锁状态
- 偏向锁信息
- 类元数据指针
例如:
创建后对象头会记录该对象对应的Class信息。
实例数据
实例数据保存真正的成员变量。
private Long id;
private String name;
}
id和name最终都会存储在实例数据区域。
对齐填充
HotSpot要求对象大小必须是8字节整数倍。
如果对象长度不足8字节对齐要求,则自动补齐。
4️⃣ 算法分析
指针碰撞(Bump The Pointer)
当堆内存规整时,JVM采用指针碰撞方式分配对象。
↓↓↓↓↓↓↓↓
###########
↑
分配指针
↓↓
向后移动对象大小
例如对象大小为32字节。
只需将指针向后移动32字节即可完成分配。
空闲列表(Free List)
如果堆内存不连续。
例如CMS垃圾回收器产生大量碎片。
此时JVM采用空闲链表方式寻找可用空间。
空闲块2
空闲块3
↓
找到满足大小要求的区域
然后完成对象分配。
5️⃣ 执行流程
↓
检查Class是否已加载
↓
计算对象大小
↓
TLAB分配?
↓
是
↓
线程私有区域分配
↓
否
↓
CAS更新堆指针
↓
零值初始化
↓
对象头初始化
↓
执行构造方法
↓
返回对象引用
零值初始化
JVM会先把所有成员变量赋默认值。
boolean flag = false;
Object obj = null;
然后再执行构造方法中的赋值逻辑。
6️⃣ 实际案例
案例一:单线程创建对象
此时对象直接在Eden区分配。
分配完成后返回对象引用地址。
案例二:多线程同时创建对象
Thread-2 → new User()
Thread-3 → new User()
如果多个线程同时修改堆顶指针。
可能出现:
因此JVM必须保证分配过程线程安全。
CAS保证线程安全
JVM内部使用CAS更新堆顶指针。
newTop = oldTop + objectSize
CAS(oldTop,newTop)
如果CAS失败。
说明其他线程已经修改成功。
当前线程重新尝试即可。
TLAB保证线程安全
JVM真正常用的是TLAB机制。
TLAB全称:
即线程本地分配缓冲区。
每个线程在Eden区拥有一小块私有空间。
↓
TLAB1 → Thread1
TLAB2 → Thread2
TLAB3 → Thread3
线程优先在自己的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中完成无锁分配。
相关文章
-
有哪些数据安全的方案?
1、锁 2、单线程,比如:redis 3、不共享变量,比如:ThreadLocal 4、原子操作,比如:AtomicInteger 5、不可变模式,比如:一旦创建不能修改,修改就会再次创建一个新的对象,String 6、读写分离,写时复制,比如:CopyOnWriteArrayList
NEW个对象 2025-02-11
-
JVM对象内存分配在哪里?从堆内存到栈上分配全面解析
JVM对象内存分配在哪里?从堆内存到栈上分配全面解析
NEW个对象 2026-06-10
-
JVM常见的配置参数
JVM常见的配置参数
NEW个对象 2025-01-09