Java并发编程之synchronized(一)

82 阅读2分钟

Java对象

Java中一个对象在内存中分为三部分:对象头、实例数据、对齐填充。

对象头

对象头主要分为Mark Word、类型指针(Class Pointer)、数组长度(Array Length)三部分。

未命名文件 (4).png

Mark Word

Mark Word中包含了对象的hashcode、对象分代年龄、偏向锁标志、锁标志、线程ID、锁记录ID等信息,synchronized锁就是通过Mark Word实现的,所以理解Mark Word对后面的内容极为重要。

hashcode是在调用对象的hashcode()方法之后才有的,如果没调用就是空的

未命名文件 (6).png

  • hashcode:存储对象的hashcode
  • age:对象分代年龄,表示被垃圾回收的次数。
  • biased_block:偏向锁标志,1表示偏向,0表示不偏向。
  • lock:锁标志,用不同的值来表示上图中的几种锁的状态。
  • thread_id:在偏向锁状态下,存储偏向的线程的ID
  • epoch:偏向锁时间戳(这个暂时没太搞明白,后续明白了再来改正)
  • 锁记录指针:在轻量级锁状态下,存储栈中锁记录的地址。
  • 重量级锁指针:在重量级锁状态下,在操作系统中会生成一个Monitor对象,这个指针就是指向这个Monitor的地址
类型指针

类型指针用来指向类在内存中的位置,在64位虚拟机中长度是64bits,但是JVM默认开启了指针压缩,长度变成了32bits,指针压缩可以通过-XX:-UseCompressedOops参数来进行设置。

数组长度

只有数组类型才有这部分,32位和64位虚拟机中长度都是32bits

实例数据

实例数据存储的是对象的属性信息和父类的属性信息。

对齐填充

JVM要求对象的起始地址必须是8的整数倍,所以当对象长度不能满足要求时,就需要额外的字节来进行对齐填充到8的整数倍。

查看对象信息

openjdk提供了一个依赖可以方便查看对象信息:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(ClassLayout.parseInstance(test).toPrintable());
    }
}

64位虚拟机关闭指针压缩情况下,输出结果

Java对象信息.png

Tips:这里使用的是当前最新版本0.17,可以看到输出内容的可读性是比较高的,标识出了markclass、实例数据、对齐填充(对象类型还会有array length),value部分也会标识出锁的状态以及分代年龄,并且是十六进制格式。

测试0.14版本发现value部分是二进制格式,并且可读性不像图中这么好,但是表示的内容都是一样的,这个根据自己情况选择不同版本就好。

另外,虚拟机默认开启了指针压缩,如果设置了关闭或者使用的32位虚拟机,输出结果可能会有所不同。