JVM对象内存分配是按需划分、分而治之、线程隔离、快速响应的协同机制:先校验类加载与固化对象大小,再依堆规整性选择指针碰撞或空闲列表分配,结合TLAB实现线程安全,最后由代际策略(Eden/老年代直入/晋升)智能路由分配位置。

JVM 内存分配不是简单地“把对象塞进堆里”,而是一套由类结构、线程模型、GC策略和硬件特性共同约束的协同机制。它的底层逻辑核心在于:**按需划分、分而治之、线程隔离、快速响应**——既保证单个对象创建的高效性,又支撑大规模并发下的内存稳定性。
对象创建从字节码开始:类加载 + 内存预判
当执行 new 指令时,JVM 并不直接分配内存,而是先完成两件事:
- 符号引用校验:检查常量池中该类的符号引用是否已解析,若未加载,则触发类加载全过程(加载→验证→准备→解析→初始化);
- 大小固化:类加载完成后,对象实例所需内存大小就已确定(由字段类型、数量及对齐填充规则决定),无需运行时计算。
这意味着分配动作本质是“搬移固定长度的内存块”,而非动态估算,为后续高速分配打下基础。
堆内分配的三种物理路径
实际划出这块内存,取决于堆的整理状态和并发场景:
- 指针碰撞(Bump Pointer):适用于使用“复制”或“标记-整理”算法的垃圾收集器(如 Serial、ParNew、G1 的 Eden 区)。堆内存规整,仅维护一个“空闲起始指针”,分配即移动指针,原子且极快;
- 空闲列表(Free List):用于“标记-清除”类 GC(如 CMS 在并发失败后),堆内存碎片化,需遍历链表查找合适空闲块,开销略高;
- TLAB(Thread Local Allocation Buffer):每个线程私有的一小块 Eden 子区域。对象优先在 TLAB 中分配,避免多线程竞争同一指针或列表。TLAB 耗尽时才同步申请新块——这是默认开启、几乎无感却至关重要的线程安全优化。
代际策略驱动分配决策
分配位置不是固定的,而是受生命周期启发的智能路由:
- 绝大多数对象在 Eden 区诞生;
- 超大对象(如大数组)若超过
-XX:PretenureSizeThreshold阈值,直接进入 老年代,避免在新生代反复复制; - 对象在 Survivor 区每经历一次 Minor GC,年龄+1;达到
-XX:MaxTenuringThreshold(默认 15)后晋升老年代; - Minor GC 前会做“空间分配担保”检查:若老年代剩余空间
栈上分配与逃逸分析:绕过堆的隐性优化
并非所有对象都必须进堆。JVM(尤其是 HotSpot Server 模式)通过逃逸分析判断对象作用域:
- 若对象只在方法内使用、不被外部引用、不发生线程逃逸,JIT 编译器可将其 栈上分配——随栈帧自动回收,零 GC 开销;
- 进一步,若对象成员变量也满足条件,甚至可进行 标量替换:拆解对象,只保留必要字段存入局部变量表,彻底消除对象实例本身。
这类优化透明生效,但依赖 JVM 启动参数(如 -XX:+DoEscapeAnalysis)和运行时 profiling 数据,不保证 100% 触发。
元空间与直接内存:脱离堆的延伸管理
方法区在 JDK 8+ 已迁移至本地内存(元空间),不再受 JVM 堆大小限制,但受系统内存制约;
- 类元数据、常量池等分配在元空间,OOM 表现为
java.lang.OutOfMemoryError: Metaspace; -
直接内存(DirectByteBuffer)由
Unsafe.allocateMemory()或 NIO 分配,属于操作系统内存,不受 GC 管理,但可通过 Cleaner 机制间接释放——若频繁创建未及时清理,易引发系统级 OOM。

















