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

Java对象的内存结构

文章目录

  • 前言
  • 一、对象头
    • 1.1 Mark Word
    • 1.2 类型指针
    • 1.3 数组长度
  • 二、实例数据
    • 2.1 字段存储顺序
    • 2.2 字段对齐
    • 2.3 字段重排序
  • 三、对齐填充
    • 3.1 对齐的必要性
    • 3.2 填充计算
    • 3.3 减少内存浪费
  • 总结


前言

Java对象在堆内存中的存储并不是简单地将字段值连续排列,而是遵循特定的内存布局规则。一个Java对象在内存中主要由三个部分组成:对象头实例数据对齐填充


一、对象头

对象头是Java对象内存结构中的第一部分,包含了对象运行时所需的重要信息。在HotSpot虚拟机中,对象头主要由两部分组成:Mark Word类型指针

1.1 Mark Word

Mark Word是对象头的核心组成部分,用于存储对象运行时的元数据信息。其大小在32位系统中为4字节,在64位系统中为8字节。

Mark Word中存储的信息包括:

  • 哈希码:对象的身份哈希码
  • GC分代年龄:对象经历的垃圾回收次数
  • 锁状态标志:表示对象当前的锁定状态
  • 线程持有的锁:记录持有该对象锁的线程信息
  • 偏向线程ID:在偏向锁状态下记录偏向的线程

Mark Word的存储信息会根据对象状态的不同而发生变化:

无锁状态:存储哈希码、分代年龄
偏向锁:存储偏向线程ID、偏向时间戳、分代年龄
轻量级锁:存储指向栈中锁记录的指针
重量级锁:存储指向monitor对象的指针

1.2 类型指针

类型指针指向对象的类元数据,JVM通过这个指针确定该对象属于哪个类。在64位系统中,如果开启了压缩指针(-XX:+UseCompressedOops),类型指针占用4字节;否则占用8字节。

1.3 数组长度

如果对象是数组类型,对象头还会包含一个额外的4字节用于存储数组长度。这是因为JVM可以通过普通对象的类型指针获取对象大小,但数组对象的大小需要通过数组长度来确定。

对象头大小计算示例
普通对象(64位系统,开启压缩指针):
Mark Word(8字节) + 类型指针(4字节) = 12字节数组对象(64位系统,开启压缩指针):
Mark Word(8字节) + 类型指针(4字节) + 数组长度(4字节) = 16字节

二、实例数据

实例数据部分是对象真正存储有效信息的地方,包含了对象中定义的各种类型的实例字段,无论是从父类继承的还是在子类中定义的字段。

2.1 字段存储顺序

HotSpot虚拟机默认的分配策略会将相同宽度的字段分配到一起存放,其中父类定义的变量会出现在子类变量之前。具体的存储顺序为:

  1. 双精度类型(double)和长整型(long):8字节
  2. 整型(int)和浮点型(float):4字节
  3. 短整型(short)和字符型(char):2字节
  4. 字节型(byte)和布尔型(boolean):1字节
  5. 引用类型(reference):在开启压缩指针的64位系统中为4字节,否则为8字节

2.2 字段对齐

为了提高访问效率,JVM会对字段进行对齐处理。例如,如果一个long类型字段没有对齐到8字节边界,可能会在前面插入填充字节。

public class FieldLayoutExample {private boolean flag;    // 1字节// 此处可能插入3字节填充private int number;      // 4字节private long timestamp;  // 8字节private String name;     // 4字节(压缩指针)或8字节
}

2.3 字段重排序

JVM允许通过参数控制字段的重排序行为:

  • -XX:FieldsAllocationStyle=0:按照字段在代码中出现的顺序分配
  • -XX:FieldsAllocationStyle=1:按照字段类型大小分配(默认)
  • -XX:FieldsAllocationStyle=2:按照JVM优化策略分配

三、对齐填充

对齐填充不是必然存在的,它仅仅起着占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是说任何对象的大小都必须是8字节的整数倍。

3.1 对齐的必要性

内存对齐主要出于以下几个原因:

  1. 提高访问效率:CPU访问对齐的数据比访问未对齐的数据要快
  2. 简化内存管理:统一的对象大小规则简化了垃圾收集器的实现

3.2 填充计算

理解对齐填充机制的核心价值在于:除了对象末尾的对齐填充外,字段之间也可能存在填充。不同的字段排列顺序会导致不同的内部填充,从而显著影响总的内存使用量。

// 对齐填充示例
public class PaddingExample {private byte a;     // 1字节private int b;      // 4字节例如:对象头12字节 + 实例数据5字节 = 17字节需要填充7字节使总大小为24字节(8的倍数)
}

3.3 减少内存浪费

理解对齐填充机制有助于我们优化对象设计,减少不必要的内存浪费:
优化前:内存浪费严重

public class BeforeOptimization {private byte b1;private long l1;private byte b2;private int i1;
}

内存布局如下:

对象头:     12字节 (地址0-11)
填充:       4字节 (地址12-15,为了让long对齐到8字节边界)
long b:     8字节 (地址16-23)
int d:      4字节 (地址24-27,自然4字节对齐)
byte a:     1字节 (地址28)
byte c:     1字节 (地址29)
对齐填充:    6字节 (地址30-35,凑到8的倍数)
总计:       36字节

优化后:内存使用更紧凑


public class AfterOptimization {private long l1;private int i1;private byte b1;private byte b2;
}

内存布局如下:

对象头:     12字节
填充:       4字节 (让long对齐到16字节位置)
long b:     8字节
int d:      4字节  
byte a:     1字节
byte c:     1字节
填充:        2字节
总计:       32字节

优化原理:通过将相同大小的字段聚集在一起(如两个byte字段相邻),可以减少字段间的内部填充,虽然本例中总大小相同,但在更复杂的对象中,合理的字段排序可以显著减少内存占用。


总结

Java对象在内存中的布局由对象头、实例数据和对齐填充三部分组成,这种设计兼顾了运行时效率和内存管理的需要。

对象头承载了对象运行时的重要元数据,包括锁信息、哈希码、GC年龄等,是JVM实现对象管理和同步机制的基础。

实例数据部分存储了对象的真实数据,JVM通过合理的字段排序和对齐策略,在保证数据访问效率的同时尽可能减少内存浪费。

对齐填充虽然仅仅起着占位符的作用,但它确保了对象大小符合JVM的管理要求,是内存管理效率的必要代价。

相关文章:

  • Git仓库大文件清理指南
  • C++测开,自动化测试,业务(第一段实习)
  • 【PyQt5】PyQt5初探 - 一个简单的例程
  • 数据结构-排序-排序的七种算法(2)
  • Google Android 14设备和应用通知 受限制的设置 出于安全考虑......
  • Office办公文档软件安装包2024版
  • Java复习Day25
  • 性能优化 - 案例篇:缓冲区
  • Vue-1-前端框架Vue基础入门之一
  • Redis 缓存穿透、缓存击穿、缓存雪崩详解与解决方案
  • c++学习值---模版
  • Java设计模式详解:策略模式(Strategy Pattern)
  • [蓝桥杯]缩位求和
  • Odoo 中SCSS的使用指南
  • Vue框架2(vue搭建方式2:利用脚手架,ElementUI)
  • Python Day39 学习(复习日志Day4)
  • 鸿蒙OSUniApp PWA开发实践:打造跨平台渐进式应用#三方框架 #Uniapp
  • 用户资产化视角下开源AI智能名片链动2+1模式S2B2C商城小程序的应用研究
  • (9)-Fiddler抓包-Fiddler如何设置捕获Https会话
  • ACL基础配置
  • 如何建设一个门户网站/市场营销毕业论文
  • 企业网站规划方案/进行网络推广
  • 我们的网站/seo费用
  • javascript做网站重要吗/百度指数的数值代表什么
  • 企业网站如何制作/合肥网站建设程序
  • zblog搭建网站/郑州seo培训班