对象存于堆中,栈仅存引用;分配采用指针碰撞或空闲列表策略;多线程通过tlab避免竞争;分配后需零值初始化、设置对象头、执行构造方法。

Java 中对象的内存分配不是简单地“new 一下就完事”,而是一套由 JVM 主导、涉及多个内存区域协同工作的机制。理解它,关键在于抓住在哪分配、怎么分配、如何保障安全这三个核心问题。
对象存在哪?堆是主战场,栈里只存引用
所有通过 new 创建的对象实例(包括数组),其实际数据都存储在堆(Heap)中——这是线程共享、GC 管理的核心区域。而方法内部声明的变量(比如 Person p = new Person(); 中的 p),只是个4 字节(开启压缩指针时)或 8 字节的引用值,它被压入当前线程的虚拟机栈中,指向堆里那个真实对象的起始地址。这种“栈存引用、堆存实体”的分离设计,是 Java 内存模型的基础。
内存怎么分?看垃圾收集器,选指针碰撞 or 空闲列表
堆内存怎么划出一块给新对象?JVM 不是随机找空地,而是根据堆的规整程度,选择两种策略之一:
- 指针碰撞(Bump the Pointer):适用于 Serial、Parallel 等使用“标记-整理”或“复制算法”的收集器。此时堆内存是规整的——已用区和空闲区泾渭分明,中间一个指针作分界。分配就是把指针“向前一推”,速度极快,几乎没有额外开销。
- 空闲列表(Free List):适用于 CMS、G1(部分场景)等基于“标记-清除”的收集器。堆内存碎片化严重,JVM 必须维护一张动态更新的空闲块列表,每次分配都要遍历查找足够大的块。效率低于指针碰撞,但能应对不规整内存。
多线程下怎么不抢?TLAB 是默认答案
多个线程同时 new 对象,如果都去争抢同一个堆指针或同一张空闲列表,必然引发竞争。JVM 的主流解法是TLAB(Thread Local Allocation Buffer):每个线程在 Eden 区预先划出一小块私有缓冲区(默认开启,可通过 -XX:+UseTLAB 控制)。线程优先在自己的 TLAB 里分配,几乎无锁。只有当 TLAB 不够用时,才触发同步机制(如 CAS 更新全局指针)。实测显示,启用 TLAB 可使分配吞吐量提升 40% 以上。
分配之后还干啥?零值初始化 + 对象头设置 + 构造方法执行
内存分配只是起点,后续还有三步不可少:
-
零值初始化:将分配到的内存空间(不包括对象头)全部设为 0(
null、0、false等),确保字段即使不显式赋值也能安全读取; - 设置对象头:写入 Mark Word(含哈希码、GC 年龄、锁标志位)和 Klass Pointer(指向方法区中的类元数据);
-
执行
<init></init>方法:这才是程序员看到的“构造函数”,负责按业务逻辑真正初始化字段值。
从 JVM 视角,对象在第三步完成时才算真正诞生;从 Java 代码视角,必须等到第四步结束,对象才处于可用状态。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











