Java对象实例分配在堆中,但创建需先完成类加载,再经指针碰撞/空闲列表/TLAB分配内存,对象结构含对象头、实例数据和对齐填充,引用存于栈帧,方法代码存于元空间。

Java 对象在内存中主要分配在堆(Heap)里,但整个过程涉及多个 JVM 内存区域协同工作,并不是简单“一创建就扔进堆”。关键在于:对象实例本身在堆中,而它的引用、方法调用上下文、类元数据等分别落在栈、方法区(或元空间)、对象头等位置。
对象创建前:类必须先加载到位
执行 new User() 时,JVM 第一步不是分配内存,而是检查 User.class 是否已加载、链接、初始化。如果没加载,会触发类加载机制——把字节码从磁盘读入方法区(JDK 8+ 是元空间),并解析静态字段、常量池、方法签名等。只有类准备好了,后续步骤才开始。
对象内存分配:堆中划出一块空间
类加载检查通过后,JVM 才真正为对象分配堆内存。具体方式取决于垃圾收集器使用的算法:
- 指针碰撞(Bump the Pointer):适用于内存规整的场景(如 Serial、ParNew 收集器)。堆被分为“已使用”和“空闲”两块,中间一个指针作分界;分配时只需将指针向空闲侧移动对应大小即可,效率极高。
- 空闲列表(Free List):适用于内存不规整的场景(如 CMS 收集器)。JVM 维护一张记录可用内存块的列表,分配时从中查找足够大的空闲块,再更新列表。
- TLAB(线程本地分配缓冲区):为避免多线程竞争堆内存,每个线程在 Eden 区预分配一小块私有空间。大多数小对象直接在 TLAB 中分配,无需同步,大幅提升并发性能。
对象在堆中的结构:不只是字段值
成功分配内存后,对象在堆中形成一个完整结构,包含三部分:
立即学习“Java免费学习笔记(深入)”;
- 对象头(Object Header):12 字节(64 位 JVM 开启指针压缩时),含 Mark Word(存储哈希码、锁状态、GC 分代年龄等)和类型指针(指向元空间中的类元数据)。
-
实例数据(Instance Data):按字段宽度和继承顺序排列(父类字段在前),包括基本类型值(如
int占 4 字节)和引用类型地址(如String name存的是 4 字节对象指针)。 -
对齐填充(Padding):保证对象总大小是 8 字节的整数倍,便于 CPU 高效读取。例如一个只有
int age的对象,实际占用 16 字节(对象头 12 + age 4 + 填充 0?不,需补到 16,所以填 0 字节;若含long等 8 字节字段,则可能补 4 字节)。
引用与访问:栈里存地址,堆里存内容
对象本身在堆中,但代码中使用的变量(如 User u = new User();)中的 u 是一个引用,它存在当前线程的虚拟机栈帧里,内容是堆中该对象的起始地址。方法调用时,栈帧还保存局部变量、操作数栈、返回地址等;而对象的方法代码本身,存储在元空间中——调用时靠对象头里的类型指针定位到对应类的方法表,再跳转执行。


















