跳转到主内容
极星编程网:以代码为星,赴技术山海!

怎么通过内存地址引用理解Java对象在堆内存中的存储结构

Java对象在堆中按对象头、实例数据、对齐填充三部分连续布局;对象头含Mark Word和Klass Pointer,实例数据按字段宽度降序排列并继承优先,对齐填充确保8字节整数倍。 Java中没有直接暴露内存地址的操作,所以无法像C/C++那样通过指针或地址值去“看到”对象在堆中的真实位置。但理解对象在堆内存中的逻辑存储结构,对掌握GC、对象布局、锁升级、序列化等机制非常有帮助——关键不是记住地址数字,而是把握 对象头、实例数据、对齐填充 这三部分的组织逻辑和作用。 对象在堆中不是“裸数据”,而是有固定结构的内存块 每个Java对象(除数组外)在堆中占用连续的一段内存,其内部按JVM规范划分为三个区域: 对象头(Header) :包含Mark Word(存储哈希码、GC分代年龄、锁状态标志、线程持有的锁指针等)和Klass Pointer(指向类元数据的指针,用于确定对象属于哪个类);在64位JVM开启指针压缩(默认开启)时,Klass Pointer占4字节 实例数据(Instance Data) :真正存放成员变量的地方,按字段宽度从大到小排序(long/double → int/float → short/char → byte/boolean),父类字段排在子类字段前面 对齐填充(Padding) :保证整个对象大小是8字节的整数倍(即内存对齐),便于CPU高效读取;这部分无实际语义,纯属填充字节 内存地址本身不可见,但可通过工具间接观察布局 虽然 Java语言层面禁止获取对象地址 (如没有 & 运算符),但借助JOL(Java Object Layout)工具可以精确计算并打印对象的内存布局: 添加依赖:
org.openjdk.jol:jol-core
调用
ClassLayout.parseInstance(obj).toPrintable()
即可输出字段偏移、大小、对齐信息 例如一个空的
Object
实例,在64位+指针压缩下通常占12字节(8字节Mark Word + 4字节Klass Pointer),再加4字节对齐填充 → 共16字节 引用变量本质是“句柄或直接指针”,不等于地址值 声明
MyClass obj = new MyClass();
时,
obj
是栈上的局部变量,它存储的不是内存地址数字,而是JVM内部用于定位堆中对象的 引用值 。这个值的具体形式取决于JVM实现: Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 立即学习 “ Java免费学习笔记(深入) ”; HotSpot采用直接指针方式 :栈中引用直接保存对象在堆中的起始地址(即指向对象头);访问快,但GC移动对象时需更新所有引用 (历史上也有句柄池方式:引用指向句柄池中的条目,句柄再存对象地址和类型指针;现代HotSpot已不用) 这个“地址”是JVM管理的逻辑地址,用户代码既不能打印它,也不能参与运算 —— 它只是JVM内部调度的桥梁 理解结构比纠结地址更有实际价值 知道对象怎么放,能帮你解释很多现象: 为什么
new Object()
占16字节,而
new Long(1)
占24字节?(对象头12 + long字段8 - 对齐后共24) 为什么把
long
字段放在前面能略微提升缓存命中率?(减少CPU预取时跨缓存行) 为什么
synchronized
锁对象时,会修改Mark Word里的锁标志位?(因为锁信息就存在对象头里) 为什么使用
Unsafe.objectFieldOffset()
能拿到字段相对于对象起始的偏移量?(正是基于上述布局规则计算得出)

相关文章