当前位置: 首页 > news >正文

Java对象在内存中的布局详解

1、Java 对象内存布局(HotSpot 虚拟机)

        在 ​HotSpot 虚拟机​ 中,一个 Java 对象在堆内存中的存储布局可以分为以下几个部分:

        1、对象头(Object Header)​

                对象头是对象内存布局中最重要的部分之一,它存储了关于对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态等。

                对象头又分为两部分(在 64 位 JVM 中):

组成部分说明
​Mark Word(标记字段)​​存储对象自身的运行时数据,如:哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID等
​Klass Pointer(类型指针)​​指向对象所属类的元数据(即 Class 对象)的指针,JVM 通过它确定该对象是哪个类的实例

注意:

        1、对象头是实现 synchronized、hashCode、垃圾回收、锁升级等机制的基础!

        2、如果对象是一个 ​数组,对象头中还会多一个部分:​数组长度(Array Length)​​

        1、Mark Word(标记字段)详解(以 64 位 JVM 为例)

                Mark Word 是对象头的一部分,它是一个 ​动态的数据结构,根据对象当前的状态(是否偏向锁、轻量级锁、重量级锁等),​存储的内容会动态变化

锁状态存储内容简述
​无锁状态​对象的 hashCode、GC 分代年龄
​偏向锁​偏向线程 ID、偏向时间戳、对象分代年龄
​轻量级锁​指向栈中锁记录的指针
​重量级锁​指向 Monitor(管程/互斥锁)的指针
​GC 标记​与垃圾回收相关的标记(如三色标记)

        Mark Word 利用了 ​位运算与掩码,在一个 64-bit 的长整型中存储多种信息,非常精巧高效。

       

        2、Klass Pointer(类型指针)

                是一个指针,指向该对象所属的 ​类元数据(即 .class 对象)​,JVM 通过这个指针判断该对象是哪个类的实例。

                在开启压缩指针(Compressed OOPs,默认开启)的情况下,占 ​4 字节;未开启则占 ​8 字节

通过 Klass Pointer,JVM 知道这个对象是哪个 Class 的实例,从而可以找到方法、字段、父类等信息。

        2、实例数据(Instance Data)​

                这是对象真正存储的 ​有效信息,也就是代码中定义的各种类型的字段(成员变量)​,包括:

                1、从父类继承的字段

                2、当前类中定义的字段

                3、不同数据类型的字段(如 int、long、引用类型等)

        实例数据部分是对象“业务数据”的存储区域,它的排列顺序受 JVM 实现与字段定义顺序影响,也可能被优化(如字段重排序)。​

JVM 在布局时,可能会按照以下原则排列实例数据:

  • 相同大小的字段尽量放在一起
  • 父类字段在前,子类字段在后
  • 可能进行 ​字段重排序以优化内存对齐
         3. ​对齐填充(Padding)​

                对齐填充不是必须的,但通常存在。

                HotSpot 虚拟机要求 ​对象的大小必须是 8 字节的整数倍(即对象总大小对齐到 8 字节)​,这是为了提高内存访问效率(CPU 读取内存通常是按 8 字节对齐的)。

                如果对象头 + 实例数据的总大小不是 8 的倍数,JVM 会在对象末尾填充一些 ​无意义的空白字节(Padding)​,以达到对齐的目的。

对齐填充 ​不存储任何有用信息,仅仅是为了内存布局优化。​

2、Java 对象内存布局总结(图示)

        下面是 ​一个普通 Java 对象在内存中的布局(64 位 JVM,开启压缩指针)的简化图示:​

+---------------------------+
|      对象头 (Header)       |
|   - Mark Word (8字节)     |  --> 哈希、GC年龄、锁状态等
|   - Klass Pointer (4字节) |  --> 指向 Class 元数据
|                           |  (如果是数组,还会有数组长度字段 4字节)
+---------------------------+
|      实例数据 (Fields)     |  --> 你定义的成员变量(int, 引用等)
+---------------------------+
|     对齐填充 (Padding)     |  --> 保证总大小是 8 字节倍数(可能没有)
+---------------------------+

3、对象大小估算(示例)

        估算一个 Java 对象在内存中占用的空间大小示例

public class User{int id;         // 4 字节String name;    // 4 字节(引用类型,压缩指针下)boolean male;   // 1 字节int age;        // 4 字节
}

注意:这里说的是对象自身占用的内存,不包括它引用的对象(比如 name 是 String,String 对象在堆上另存)

        对象布局分析(64 位 JVM,开启压缩指针):

成部分大小(字节)说明
​对象头​12 字节Mark Word(8) + Klass Pointer(4)
​实例数据​4 (int id) + 4 (引用 name) + 1 (boolean male) + 4 (int age) = 13 字节
​对齐填充​3 字节总计 12 + 13 = 25,不是 8 的倍数,填充到 28 字节(32 - 25 = 3)

大约占用 28 字节(实际可能略有差异,依赖 JVM 实现)​

        4、特殊对象:数组对象的内存布局

                如果对象是一个 ​数组,比如 int[]String[],那么对象头中会多一个字段:数组长度(Array Length,4 字节)​

+---------------------------+
|        对象头             |
|   - Mark Word (8字节)     |
|   - Klass Pointer (4字节) |
|   - 数组长度 (4字节)      |  <-- 仅数组对象有
+---------------------------+
|      数组元素数据         |  --> 比如 int[] 就是连续的 int 值
+---------------------------+
|     对齐填充 (如有)       |
+---------------------------+

数组对象比普通对象多存储一个长度信息,占用额外 4 字节(压缩指针下)。

2、总结:

组成部分说明是否必有大小(64位,压缩指针)
​对象头(Header)​​包括 Mark Word 和 Klass Pointer✅ 必有通常 12 字节(8 + 4)
​Mark Word​存储哈希、GC 年龄、锁状态等动态信息✅ 是对象头的一部分8 字节
​Klass Pointer​指向该对象的类元数据(Class)✅ 是对象头的一部分4 字节(可开启/关闭压缩)
​数组长度(仅数组)​​数组对象才有,表示数组长度❌ 仅数组对象4 字节
​实例数据(Fields)​​对象的成员变量(包括继承的字段)✅ 必有依据字段类型而定
​对齐填充(Padding)​​保证对象总大小是 8 字节对齐❌ 可能没有0~7 字节

3、补充:

主题说明
​synchronized 的实现​依赖对象头中的 Mark Word 实现锁状态记录
​hashCode() 的默认实现​默认与对象头中的 Mark Word 相关(可重写)
​垃圾回收与对象年龄​对象头中存储 GC 分代年龄,用于判断是否进入老年代
​锁升级(偏向锁、轻量级锁、重量级锁)​​基于对象头 Mark Word 中的状态标志实现
​对象内存大小查看工具​如 ​JOL(Java Object Layout)​,可以精确查看对象布局与大小

        ​Java 对象在内存中的布局分为对象头(包括 Mark Word 和 Klass Pointer)、实例数据(成员变量)、对齐填充三部分,其中对象头是实现锁、GC、哈希等机制的核心对象大小受字段类型、对齐规则等影响

http://www.xdnf.cn/news/1467415.html

相关文章:

  • 【mysql】SQL查询全解析:从基础分组到高级自连接技巧
  • 如何将联系人从 iPhone 转移到 Redmi 手机
  • 亲戚关系计算器,秒懂全家称呼!
  • 基于YOLO目标检测模型的视频推理GUI工具
  • 超越自动化:为什么说供应链的终局是“AI + 人类专家”的混合智能?
  • Web服务与Nginx详解
  • 【服务器】英伟达M40显卡风冷方案心得
  • Git 工具的「安装」及「基础命令使用」
  • 从零到上线:Docker、Docker Compose 与 Runtime 安装部署全指南(含实战示例与应用场景)
  • 小团队如何高效完成 uni-app iOS 上架,从分工到工具组合的实战经验
  • DL3382P6平替RClamp3382P.TCT
  • JavaWeb —— 异常处理
  • iPhone17全系优缺点分析,加持远程控制让你的手机更好用!
  • Ubuntu 18.04 上升级 gcc 到 9.4
  • 敏捷开发-Scrum(下)
  • 服务器为啥离不开传感器?一文看懂数据中心“隐形守护者”的关键角色
  • 【前端】使用Vercel部署前端项目,api转发到后端服务器
  • 数据结构初阶:树的相关性质总结
  • 如何使用自签 CA 签发服务器证书与客户端证书
  • 假设一个算术表达式中包含圆括号、方括号和花括号3种类型的括号,编写一个算法来判别,表达式中的括号是否配对,以字符“\0“作为算术表达式的结束符
  • 【Linux系统】POSIX信号量
  • Jenkins环境搭建与使⽤
  • C语言(长期更新)第15讲 指针详解(五):习题实战
  • Kimi K2-0905重磅发布:月之暗面再次引领AI编程新纪元
  • 【Rust 入门】01. 创建项目
  • Rust 的生命周期与借用检查:安全性深度保障的基石
  • 极快文本嵌入推理:Rust构建高性能嵌入推理解决方案
  • Qoder 全面解析:三大模式与开发者实战指南
  • 【硬件笔记】负载是如何烧MOS的?
  • DAY1:错题日记