new指令触发的内存分配流程为:先检查类是否已加载、解析、初始化;再在堆中尝试分配空间(优先tlab,失败则cas重试或gc);最后写入对象头并插入storestore屏障确保可见性。

new指令触发的内存分配流程
执行 new 指令时,JVM 不是简单地“划一块内存出来”,而是走一套严格受控的路径:先检查类是否已加载、解析、初始化;再在堆中尝试分配空间;最后填充对象头并调用构造方法。整个过程必须保证线程安全、内存对齐、GC 可见性。
关键点在于:分配动作本身由 JIT 编译后的机器码直接控制(HotSpot 中常内联为 fast_eden_allocate),而非每次都走解释器慢路径。但前提是该类已通过类加载子系统完成 linking 和 initialization,否则会先触发类加载流程,阻塞分配。
- 若类尚未初始化,
new会先触发ClassLoader::initialize_class,可能引发死锁(比如静态块互相依赖) - Eden 区空间不足时,不立即 GC,而是先尝试
TLAB(Thread Local Allocation Buffer)分配;失败再触发eden_gc或升级为full gc - 64 位 JVM 默认开启压缩指针(
-XX:+UseCompressedOops),对象头中Klass Pointer占 4 字节而非 8 字节,直接影响分配速度和内存布局
对象头写入与内存屏障
分配成功后,JVM 必须立刻写入对象头(Mark Word 和 Klass Pointer),并确保其他线程能正确看到这个“半初始化”对象——这正是内存屏障(storestore)起作用的地方。
如果不加屏障,编译器或 CPU 可能重排序:先写实例字段,再写 Klass Pointer。此时若另一线程刚好读到该地址(比如通过逃逸分析外泄的引用),就会拿到一个 Klass Pointer 为空的对象,触发 NullPointerException 或更隐蔽的崩溃。
-
Mark Word初始值通常为 0x1(无锁状态)、0x0(偏向锁未启用时),具体取决于 JVM 启动参数(如-XX:-UseBiasedLocking) - HotSpot 在
InstanceKlass::allocate_instance中完成头写入,并在返回前插入OrderAccess::storestore() - 逃逸分析开启(
-XX:+DoEscapeAnalysis)时,部分new可能被完全消除,根本不分配堆内存
构造方法调用不是 new 的一部分
new 指令只负责分配 + 初始化对象头,不执行任何 Java 层逻辑。构造方法(<init></init>)是后续 invokespecial 指令做的事——两者在字节码层面严格分离。
这意味着:如果构造方法抛出异常,对象已经分配在堆上,只是尚未完成初始化。GC 能识别这种“正在构造”的对象(通过栈帧中的局部变量表引用),不会误回收;但若构造中发生 OOM,JVM 会清理已分配内存并抛出 OutOfMemoryError,不会留下脏数据。
- 字节码中
new和dup+invokespecial总是成对出现,dup是为了保留一份引用供后续使用 - 构造方法内对
this的赋值(如this.field = value)属于普通字段写入,受制于 volatile 语义和 final 字段的特殊规则(如final字段写入会插入storestore屏障) - JIT 编译后,空构造方法可能被完全内联,而带复杂逻辑的构造方法可能触发去优化(deoptimization)
TLAB 分配失败的真实表现
TLAB 是每个线程私有的 Eden 子区域,分配失败并不等于 Eden 满了。常见真实原因包括:TLAB 太小(-XX:TLABSize 设置过低)、线程频繁分配大对象(超过 TLAB 剩余空间)、或 TLAB 填充率低于阈值(-XX:TLABWasteTargetPercent)导致主动弃用。
此时 JVM 会退回到共享 Eden 区分配,使用 CAS 更新 top 指针。若 CAS 失败(多线程竞争),则进入慢路径:加锁或重试。这个过程在 CollectedHeap::mem_allocate 中实现,日志里会显示 TLAB allocation failure,但不会直接报错。
- 可通过
-XX:+PrintTLAB查看每个线程的 TLAB 使用统计 - 大对象(> TLAB size 或 >
-XX:PretenureSizeThreshold)直接分配到老年代,跳过 TLAB 和年轻代 GC - 禁用 TLAB(
-XX:-UseTLAB)会使所有分配都走共享区,显著增加同步开销,仅用于调试
src/hotspot/share/oops/ 和 src/hotspot/share/gc/shared/ 下,不是靠文档能记全的,得对着 instanceKlass.cpp 和 collectedHeap.cpp 一行行看。











