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)工具可以精确计算并打印对象的内存布局:
添加依赖:
调用
即可输出字段偏移、大小、对齐信息
例如一个空的
实例,在64位+指针压缩下通常占12字节(8字节Mark Word + 4字节Klass Pointer),再加4字节对齐填充 → 共16字节
引用变量本质是“句柄或直接指针”,不等于地址值
声明
时,
是栈上的局部变量,它存储的不是内存地址数字,而是JVM内部用于定位堆中对象的
引用值
。这个值的具体形式取决于JVM实现:
Eclipse导入Android或其他的JAVA项目的正确方法 WORD版
本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
下载
立即学习
“
Java免费学习笔记(深入)
”;
HotSpot采用直接指针方式
:栈中引用直接保存对象在堆中的起始地址(即指向对象头);访问快,但GC移动对象时需更新所有引用
(历史上也有句柄池方式:引用指向句柄池中的条目,句柄再存对象地址和类型指针;现代HotSpot已不用)
这个“地址”是JVM管理的逻辑地址,用户代码既不能打印它,也不能参与运算 —— 它只是JVM内部调度的桥梁
理解结构比纠结地址更有实际价值
知道对象怎么放,能帮你解释很多现象:
为什么
占16字节,而
占24字节?(对象头12 + long字段8 - 对齐后共24)
为什么把
字段放在前面能略微提升缓存命中率?(减少CPU预取时跨缓存行)
为什么
锁对象时,会修改Mark Word里的锁标志位?(因为锁信息就存在对象头里)
为什么使用
能拿到字段相对于对象起始的偏移量?(正是基于上述布局规则计算得出)
org.openjdk.jol:jol-coreClassLayout.parseInstance(obj).toPrintable()ObjectMyClass obj = new MyClass();objnew Object()new Long(1)longsynchronizedUnsafe.objectFieldOffset()