三、锁存储布局
synchronized始终与对象关联。如果方法是静态的,那么关联的对象就是类;如果该方法是非静态的,则关联的对象是实例。如果是代码块,那么就是指定的对象。很显然,锁是记录于对象中。那么问题来了,synchronized的锁具体指的是什么呢?简单理<typo id="typo-134" data-origin="解锁" ignoretag="true">解锁</typo>就是一个共享的资源,记录了谁占有它,当前状态是什么等等。我们先来分析对象在内存中是如何存储的。
在Hotspot JVM 中,Java Object对象在内存中存储中的存储布局分为三个区域,分别是对象头、示例数据、对象填充。如图所示,数组和对象的存储布局十分相似,只是对象的头部大于数组的长度,因为数组需要存储自身的长度,为4Byte。
从图上可以看出,对象头部包括两部分,分别是对象标记和类元信息(类型指针)。对象标记,也就是Markword存储对象的 hashCode、 GC 信息和锁等信息。类元信息存储“类对象信息的指针”。在32位的 JVM 中,对象头占用8个byte,另外在64位的 JVM 占用16个字节。
如上图所示,这个是Markword类在32位的 JVM 的各种情况存储布局,Markword 里面存储的数据会随着锁标志位的变为而变化,大致存储的变化共分为五种情况。我们可以从图上看到从无锁->偏向锁->轻量级锁->重量级锁存储的变化过程,这个就是锁升级的过程。
那么问题来了,是不是所有的对象都能实现锁呢?答案是肯定的。
- 首先我们对于Java有一个共有的认知,那就是<typo id="typo-639" data-origin="所以" ignoretag="true">所以</typo>的对象都派生自Object,每个Object在内存中存储都如我们图上所示的,都有对象头,对象头中有Markword对象标记。 需要注意的是,对象存储包括Markword对象标记的实现都是native的,都是C++语言实现的对象。
- 线程在获取锁时,实际获取的是一个监视器(monitor)对象,这是一个同步对象,所有的Java Object都包含这个对象。同样的,这个对象也是native的。