java对象头

83 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第26天,点击查看活动详情

当一个线程尝试访问synchronized修饰的代码块时,它首先要获得锁,那么这个锁到底存在哪里呢?是 存在锁对象的对象头中的。 HotSpot采用instanceOopDesc和arrayOopDesc来描述对象头,arrayOopDesc对象用来描述数组类型。instanceOopDesc的定义的在Hotspot源码的 instanceOop.hpp 文件中,另外,arrayOopDesc 的定义对应 arrayOop.hpp

image.png 从instanceOopDesc代码中可以看到 instanceOopDesc继承自oopDesc,oopDesc的定义载Hotspot 源码中的 oop.hpp 文件中。

image.png

  • 在普通实例对象中,oopDesc的定义包含两个成员,分别是 _mark 和 _metadata
  • _mark 表示对象标记、属于markOop类型,也就是接下来要讲解的Mark World,它记录了对象和锁有关的信息
  • _metadata 表示类元信息,类元信息存储的是对象指向它的类元数据(Klass)的首地址,其中Klass表示 普通指针、 _compressed_klass 表示压缩类指针。
  • 对象头由两部分组成,一部分用于存储自身的运行时数据,称之为 Mark Word,另外一部分是类型指 针,及对象指向它的类元数据的指针。

关于Klass,Class这些JVM的东西,推荐去看《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》。强调一点,是第三版。第三版相比第二版改了太多东西了,我看第二版的时候总感觉讲的不够深入缺点什么东西,不过第三版补齐了很多东西。

[]("Mark Word")Mark Word

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、 线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致。Mark Word对应的类 型是 markOop 。源码位于 markOop.hpp 中。

image.png

image.png

在64位虚拟机下,Mark Word是64bit大小的,其存储结构如下

image.png

klass pointer

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的 实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。 如果应用的对 象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内 存。为了节约内存可以使用选项**-XX:+UseCompressedOops** 开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

  1. 每个Class的属性指针(即静态变量)
  2. 每个对象的属性指针(即对象变量)
  3. 普通对象数组的每个元素指针

当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对 象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。 对象头 = Mark Word + 类型指针(未开启指针压缩的情况下) 在32位系统中,Mark Word = 4 bytes,类型指针 = 4bytes,对象头 = 8 bytes = 64 bits; 在64位系统中,Mark Word = 8 bytes,类型指针 = 8bytes,对象头 = 16 bytes = 128bits;