Java对象默认在堆内存中分配,具体位置取决于分代结构(Eden/Survivor/老年代)、分配策略(指针碰撞/空闲列表)及TLAB机制;小对象优先分配在TLAB,大对象或TLAB不足时直接在Eden或老年代分配。

Java对象默认在堆内存中分配,但具体位置和方式受JVM参数、对象大小、线程状态等多重因素影响,并非简单“new一下就进老年代”。核心机制包括堆空间分代结构、分配策略(如指针碰撞/空闲列表)、以及关键优化——TLAB(Thread Local Allocation Buffer)。
堆内存分代与对象分配路径
JVM堆通常分为新生代(Eden + 两个Survivor区)和老年代。绝大多数新对象优先分配在新生代的Eden区:
- 若Eden空间足够,JVM通过“指针碰撞”(使用Serial/ParNew等基于标记-压缩的收集器时)快速完成分配:移动一个指向空闲区域起始位置的指针即可
- 若Eden已满,先触发Minor GC;GC后仍有空间则继续分配,否则大对象或长期存活对象可能直接进入老年代(如设置了-XX:PretenureSizeThreshold)
- 对象实际进入哪一代,还取决于其大小、年龄、是否开启-XX:+UseAdaptiveSizePolicy等自适应策略
TLAB:每个线程专属的“小堆块”
为避免多线程同时操作Eden区指针带来的同步开销,JVM默认启用TLAB(可通过-XX:+UseTLAB开启,默认开启)。每个线程在Eden区内独占一小块内存,用于快速分配小对象:
- 对象创建时,线程优先尝试在自己的TLAB中分配;成功则无锁、极快
- TLAB用尽时,线程会申请新的TLAB;若Eden剩余空间不足分配新TLAB,则直接在共享Eden区分配(需加锁)
- TLAB大小动态调整:JVM根据线程分配速率和浪费率(如-XX:TLABWasteTargetPercent)自动优化,默认初始值约为Eden的1%
哪些情况会绕过TLAB?
并非所有对象都走TLAB,以下情况会直接在Eden共享区(甚至老年代)分配:
立即学习“Java免费学习笔记(深入)”;
- 对象尺寸大于-XX:TLABSize或当前TLAB剩余空间(JVM内部按比例判断)
- 显式禁用TLAB(-XX:-UseTLAB),所有分配都走共享区
- 大对象(如大数组),由JVM判定后直接分配在老年代(避免频繁拷贝)
- 某些GC算法下(如ZGC、Shenandoah),分配逻辑更复杂,但依然遵循“线程本地优先”原则
如何观察与调优分配行为?
借助JVM参数可验证和调整分配策略:
- -XX:+PrintGCDetails -XX:+PrintGCTimeStamps:查看GC日志中的“allocation failure”及各代使用量
- -XX:+PrintTLAB:打印TLAB分配统计(如每线程TLAB大小、浪费率、refill次数)
- -XX:TLABSize=256k 或 -XX:TLABWasteLimit=1024:手动干预TLAB行为(一般不建议,除非压测发现严重浪费或竞争)
- JDK9+ 可用JFR(Java Flight Recorder)录制“Object Allocation Outside TLAB”事件,精准定位非TLAB分配热点
基本上就这些。TLAB是JVM默默做好的性能优化,平时不用管;但遇到高并发小对象分配瓶颈或GC异常频繁时,它往往是第一个该查的地方。










