Java中对象实例化是JVM协同类信息、内存布局、线程安全与GC策略的过程,核心为“先确定大小,再找位置,最后初始化”:类加载后计算对象头、实例数据和对齐填充的总大小;分配在Eden区,依GC算法采用指针碰撞或空闲列表;通过TLAB避免并发冲突;分配后清零内存并设置对象头;逃逸分析可能触发栈上分配或标量替换。

Java中实例化对象时,JVM在堆内存中分配空间不是简单“划一块地”就完事,而是一套结合类信息、内存布局、线程安全与GC策略的协同过程。核心逻辑是:**先确定大小,再找位置,最后初始化**。
对象大小在类加载后就已确定
当执行 new Person() 时,JVM首先确认 Person 类是否已加载、验证和准备完成。此时类的结构(字段类型、数量、对齐要求)已知,虚拟机就能精确计算出该对象在堆中所需的内存大小:
- 对象头(Header):包含Mark Word(锁/哈希/GC分代年龄等)和Klass Pointer(指向方法区的类元数据),通常12字节(64位JVM开启指针压缩)
- 实例数据(Instance Data):按字段宽度(如int占4字节、long占8字节)和JVM字段重排序规则排列,含继承链上的所有非静态字段
- 对齐填充(Padding):确保对象总大小为8字节的整数倍,便于内存寻址
分配动作发生在新生代Eden区(默认路径)
绝大多数新对象都在新生代的Eden区分配。JVM采用两种主流分配策略之一,取决于堆内存是否规整(由所用GC算法决定):
- 指针碰撞(Bump Pointer):适用于Serial、Parallel等使用“标记-整理”或“复制算法”的收集器。堆内存物理连续,维护一个“空闲指针”,分配时仅将指针向后移动对象大小的距离,极快且无锁
- 空闲列表(Free List):适用于CMS、G1等使用“标记-清除”的收集器。堆内存存在碎片,JVM维护一张空闲块链表,从中查找足够大的连续空间进行分配
为避免多线程并发分配冲突,HotSpot默认启用TLAB(Thread Local Allocation Buffer):每个线程在Eden中预划一小块私有缓冲区,分配直接在本地完成,无需同步;TLAB耗尽或对象过大(通常 > TLAB剩余空间的一半)时,才在Eden共享区或直接进入老年代(大对象阈值由-XX:PretenureSizeThreshold控制)。
立即学习“Java免费学习笔记(深入)”;
分配后立即初始化,但不等于构造完成
内存分配完成后,JVM会做两件事:
- 将内存空间清零(保证字段有默认值:0、null、false),这步由分配机制自动完成,无需额外指令
- 设置对象头信息(如哈希码、GC年龄、锁标志位等)
此时对象已存在于堆中,引用变量(如Person p)还只是栈帧里的一个局部变量,它存储的是该对象在堆中的起始地址。真正调用构造方法(Person())属于后续步骤,用于给字段赋业务初值——这发生在内存分配之后,但仍是对象创建流程的一部分。
特殊情况:不一定总在堆上
虽然规范说“对象应在堆上分配”,但现代JVM(尤其是HotSpot)通过逃逸分析可优化部分对象的分配位置:
- 若对象仅在当前方法内使用、不被外部引用(未逃逸),JVM可能将其栈上分配(Stack Allocation),方法结束即自然回收
- 若对象成员可被拆解为独立标量(如Point类含x/y两个int),且未逃逸,可能触发标量替换,连对象壳都不创建,直接分配x、y到栈帧局部变量表
这类优化默认开启(-XX:+DoEscapeAnalysis),但需配合-XX:+EliminateAllocations生效,且只影响局部、短生命周期对象。绝大多数业务代码中,你写的new仍落在堆上。


















