JVM对象内存布局
本文最后更新于 2025年9月10日 下午

私有部分
程序计数器 : 通过改变这个计数器的值来选取下一条需要执行的字节码指令,存放在线程私有内存中。
Java虚拟机栈 :它的生命周期与线程相同,虚拟机栈描述的是Java方法执行的线程内存模型,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表,操作数栈,动态连接,方法出口等信息。每一个方法被调用直至执行完毕的过程,对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表 : 存放了编译期可知的各种Java虚拟机基本数据结构,对象引用和返回地址类型。在存储空间中以局部变量槽来表示。当进入方法时,局部变量所需的内存空间大小是完全确定的。
本地方法栈 : ****为虚拟机用到的本地方法服务
公有部分
堆
堆是虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。堆的唯一目的是用于存放对象实例,理论上所有的对象实例和数组都应当在堆上分配。
堆是垃圾收集器管理的内存区域。从回收内存的角度看,由于现代垃圾收集器大部分是基于分代收集理论设计的,所有Java堆中经常会出现 “新生代”,“老年代”。本质上是由于垃圾回收策略不同而创建出的不同内存管理区域。
堆中还存在多个线程私有的分批额缓冲区(TLAB)
方法区
方法区也是各个线程共享的内存区域,它用于存储被虚拟机加载的类型,常量,静态变量,以及代码缓存数据。
运行时常量池
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量与符号引用。
直接内存与本地内存
| 名称 | 是否堆外 | 是否 GC 管理 | 分配者 | 主要用途 |
|---|---|---|---|---|
| 直接内存 | 是 | 部分(引用可见) | Java(ByteBuffer) | 高性能 I/O、Netty |
| 本地内存 | 是 | 否 | JVM / JNI | 类元空间、线程栈、JNI |
直接内存是 JVM 提供给 Java 程序使用的“受控的”本地内存;而本地内存是 JVM 和本地代码自己使用的“非受控”内存。
Java对象的创建过程
由new 启动 对象的过程
- 定位所需创建类的符号引用
- 判断类是否已完成加载与初始化,若未初始化则先触发类初始化流程
- 创建对象,为新对象分配内存,在类加载过程中可以确定新对象所使用的内存大小
- 内存分配过程
- 通过指针偏移获取对应对象大小的内存空间进行创建 (如果是规整的内存布局)
- 使用标记-整理算法的收集器 选择指针碰撞
- 通过列表维护一个列表记录管理内存中的布局,查询可用大小的内存块进行分配
- 使用标记-清除算法的收集器 选择空闲列表
- 通过指针偏移获取对应对象大小的内存空间进行创建 (如果是规整的内存布局)
- 多线程情况下的内存分配
- 分配内存空间时进行同步处理—— 实际上虚拟机采用CAS + 失败重试保证更新操作的原子性
- 将内存分配动作按照线程划分到不同的空间中,即线程在java堆中预先分配的一小块内存中,当这一块内存使用完了,再使用同步锁定进行内存分配 使用tlab 可以使用-XX:+/-UseTLAB参数来设定
- 初始化过程
- 在内存分配完毕后进行 → 在使用了同步锁定时
- 在分配TLAB时直接进行了初始化
1 | |
- 对象结构

主要由三部分组成 : 对象头 , 实例数据和对齐填充
对象头结构
1 | |
markword
mark word 是位于对象最顶部的一块内存区域,它是一个复用字段,即根据标志位的不同决定了当前它所被使用的用途。
| 存储内容 | 标志位 | 状态 |
|---|---|---|
| 对象哈希码,对象分代年龄 | 01 | 未锁定 |
| 指向锁记录的指针 | 00 | 轻量级锁定 |
| 指向重量级锁的指针 | 10 | 膨胀 |
| 一般被设置为非法模式或者转发指针 | 11 | GC标记 |
| 偏向线程ID,偏向事件戳,对象分代年龄 | 01 | 可偏向 |
实例数据
实例数据是受虚拟机分配策略参数和字段在Java源码中定义顺序的应用
Hotspot 虚拟机默认的分配顺序为longs/doubles , ints , shorts/chars , bytesbooleans,oops(Ordinary Object Pointers )
相同宽度的字段总是被分配到一起存放,在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果设置+XX:CompactFields参数,则子类中较窄的变量也允许插入父类变量的空隙中节省空间
对齐填充
HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,如果没有对齐的话,就需要通过对齐填充补全
- 访问对象
java中使用引用去访问具体的对象
- 使用句柄访问,在堆中划分一块内存作为句柄池,句柄池中包含了实例数据信息和类型数据信息,这样在实例数据中就不需要存储地址类型指针
- 指针访问,需要在对象模型中放入类型指针以查询类型信息
- 句柄的优势在于引用是间接查询实例对象数据的,这意味着在GC阶段移动对象可以间接更改句柄中的实例数据地址,而不需要修改引用本身
- 指针的优势在于访问速度更快,节省了一次指针定位的时间

总结
JVM 对象的创建与内存布局,是理解 Java 程序执行原理、优化内存使用、调试性能问题的基础。在本文中,我们从运行时数据区的划分入手,梳理了线程私有与共享内存的职责与作用,随后深入剖析了对象创建的完整过程,包括类加载检查、内存分配策略(如指针碰撞与空闲列表)、线程本地缓冲(TLAB)机制与初始化流程。
在对象布局方面,我们重点讲解了对象头(Mark Word 和类型指针)的结构及其状态复用机制,分析了实例数据的内存对齐与压缩布局策略。同时,结合句柄与指针访问两种模型,比较了它们在对象定位与 GC 移动过程中的适用性和性能差异。
通过对这些机制的理解,开发者可以更准确地把握 JVM 如何管理内存、调度对象生命周期、以及背后隐藏的性能优化点。这不仅有助于编写更高效的 Java 程序,也为深入掌握 GC 策略、对象逃逸分析、内存模型优化等更高级主题打下了坚实基础。