面试知识库
进阶

对象内存布局#

一句话答案#

Java 对象三部分:对象头(Mark Word + 类型指针)+ 实例数据 + 对齐填充(8 字节对齐)。

核心要点

三部分结构:

  1. 对象头(Header)

    • Mark Word(8 字节/64位):存储哈希码、GC 年龄、锁标志位、偏向线程 ID
    • 类型指针(4/8 字节):指向类元数据(确定是哪个类的实例)
    • 数组长度(仅数组对象有,4 字节)
  2. 实例数据(Instance Data):各字段的值(按宽度排序,相同宽度的父类字段优先)

  3. 对齐填充(Padding):保证对象大小是 8 字节的整数倍

Mark Word(64位/无锁状态):

|---unused:25---|---hashcode:31---|---unused:1---|---age:4---|---biased:1---|---lock:2---|
plaintext
  • age: GC 年龄(最大15,晋升阈值)
  • lock: 01无锁/00轻量级/10重量级/11GC标记/01偏向
  • 指针压缩:-XX:+UseCompressedOops(默认开启,8字节→4字节)

对象访问定位(reference 如何找到对象):

栈上的 reference 是一个指向对象的引用,具体怎么定位有两种方式:

方式实现优点缺点
句柄访问堆中划出一块句柄池,reference 存句柄地址,句柄含「到对象实例数据的指针」+「到类型数据的指针」对象移动(GC 整理)时只改句柄里的指针,reference 本身不变,稳定多一次间接寻址,慢
直接指针(HotSpot 采用)reference 直接存对象地址,对象头的类型指针再指向方法区类元数据少一次寻址,访问快对象移动时要同步更新 reference
  • HotSpot 用直接指针,因为对象访问极其频繁,省下的那次寻址收益巨大。
  • 这也解释了为什么对象头里要有「类型指针」——直接指针方式下靠它找到类元数据。
面试回答(2分钟版)

Java对象在内存中由三部分组成:对象头、实例数据和对齐填充。对象头是最关键的部分,包含Mark Word和类型指针。Mark Word在64位JVM中占8字节,存储了哈希码、GC分代年龄、锁标志位和偏向线程ID等运行时元数据,它的内容会随着锁状态变化而变化——无锁时存hashcode和age,偏向锁时存线程ID,轻量级锁时存指向栈中锁记录的指针,重量级锁时存指向Monitor的指针。类型指针指向方法区中的类元数据,告诉JVM这个对象是哪个类的实例,默认开启指针压缩后从8字节压缩到4字节。实例数据部分存放对象的各个字段值,按字段宽度排序,相同宽度的父类字段优先。最后是对齐填充,保证对象大小是8字节的整数倍,这是为了提高内存访问效率。一个实际的应用是GC年龄最大只能15,因为Mark Word中age字段只有4位。

追问与易错

追问方向:

  • “什么是指针压缩?什么时候失效?”→ -XX:+UseCompressedOops 将 64 位指针压缩为 32 位(8 字节→4 字节),默认开启;当堆大小超过 32GB 时失效,因为 32 位指针偏移寻址最大只能覆盖 32GB
  • “Mark Word 在不同锁状态下存什么?”→ 无锁存 hashcode+age+01;偏向锁存线程 ID+epoch+age+01;轻量级锁存指向 Lock Record 的指针+00;重量级锁存指向 Monitor 的指针+10
  • “怎么计算一个对象占多少内存?”→ 对象头(Mark Word 8B + 类型指针 4B 压缩后)+ 实例数据(各字段大小之和)+ 对齐填充(凑 8 字节整数倍);可用 JOL(Java Object Layout)工具精确测量
  • “对象访问定位有哪几种?HotSpot 用哪种?”→ 句柄访问(reference 指向句柄池,GC 移动对象只改句柄,稳定但多一次寻址)和直接指针(reference 直接指对象,访问快但移动时要更新 reference);HotSpot 用直接指针,因为访问太频繁、省一次寻址收益大

易错点:

  • ❌ 忽略对齐填充——大小必须是 8 字节整数倍
  • ❌ 64 位 JVM 指针就是 8 字节——压缩后是 4 字节
  • ❌ “句柄访问更快”——句柄稳定但多一次间接寻址,HotSpot 反而选了访问更快的直接指针