彻底理解Java对象头

时间:2024-03-28 15:35:24

对象头介绍

Java每个对象都包含两个部分, 对象头(Object Header)对象体(Object Body). 对象头中存储代表该对象运行时的一些信息, 这部分信息与对象体的内容没有关系.

对象头被分为两个部分, 第一个部分是Mark Word, 代表标记信息, 例如hash code, 锁的标志位, GC分代年龄等; 第二个部分是Class Word, 代表类型信息, 该部分是一个指针, 指向方法区(元数据空间) 中的实际类型.

在32位虚拟机中, 对象头占64位, 其中Mark WordClass Word各占32位. 而在64位虚拟机中, 不开启指针压缩的情况下对象头占128位, Mark WordClass Word各占64位. 开启指针压缩之后, Class Word占32位, 对象头减少到占128位.

Mark Word对应到C++的代码marOop.hpp, Class Word对应到C++的代码kclass.hpp
关于文章推荐两篇, 其中一篇是英文, 相对简单, 另一篇是中文, 深入到源码

彻底理解Java对象头

对象头详细结构 – 32位虚拟机

在32位的虚拟机中, 对象头的信息如下, 占用64比特(8字节), 数组额外占用4个字节表示长度.
彻底理解Java对象头
由于在Mark Word中要存储不同的多种信息, 按照对象状态的不同, 又可以细分为5种状态的Mark Word.

  1. Normal – 普通状态, 由25位哈希码, 4位GC分代年龄, 1位偏向锁标识, 2位锁标志(01)组成.
  2. Biased – 偏向状态, 由23位线程标识, 2位时间戳, 4位GC分代年龄. 1位偏向锁标识, 2位锁标识(01)组成.
  3. Lightweight Locked – 轻量级锁状态, 由30位指针(指向锁记录), 2位锁标识(00)组成.
  4. Heavyweight Locked – 重量级锁状态, 由30位指针(指向重量级锁的monitor, monitor是一个数据结构, 会存储其他抢锁的线程), 2位锁标识(10)组成.
  5. Mared for GC – 待GC回收状态, 只有最后2位锁标识(11)有效.

注意Normal State和Biased State的锁标志位都是01, 而是否偏向锁根据 biased_lock 判断.

Normal State

  • hash code 是懒初始化, 它只会在调用 System#identityHashCode(Object) 方法之后被初始化. 在非普通状态下, hash code 将不在Mark Word中存储, 它会被存储到monitor
  • GC分代年龄是一个数字, 如果它超过了 Tenuring-Threshold则会被移到老年代.
  • biased_lock, 如果偏向锁启用, 则该位置是1, 否者是0

Normal State => biased_lock = 0, lock = 01

Biased State

  • thread 是偏向的线程, 对象头中存储的是线程id
  • epoch 是存储在 thread 位置线程的标识.

Biased State => biased_lock = 1, lock = 01

Lightweight State

  • ptr_to_lock_record, 指向锁记录(存储在当前线程的栈帧), 在锁记录中存储了Mark Word的拷贝. 使用CAS更新该30位, 更新成功则表示获取了轻量级锁.

Heavyweight State

  • ptr_to_heavyweight_monitor是一个指针, 指向 monitor.

对象头详细结构 – 64位虚拟机

在64为虚拟机中, 对象头会变为128比特(16字节), 数组额外占用4个字节表示长度.
彻底理解Java对象头
在开启指针压缩之后, 对象头 会变为12字节. 数组还是会额外占用4个字节表示长度.
彻底理解Java对象头

写在最后的话

理解对象头是为了更好的理解Synchronized, 文中难免有纰漏的地方, 欢迎指正.