Java锁-Synchronized深层剖析 (4)

前面Java对象的部分,我们提到了对象是由对象头,实例数据,对齐填充三个部分组成。其中后两者已经进行了较为充分的说明,而对象头还没有进行任何解释,而锁的实现就要靠对象头完成

对象头由两到三个部分组成:

Mark Word:存储对象hashCode,分代年龄,锁类型,锁标志位等信息(长度为JVM的一个字大小)

Class Metadata Address:类型指针,指向对象的类元数据(JVM通过这个指针确定该对象是哪个类的实例,指针的长度为JVM的一个字大小);

Array Length:[只有数组对象有该部分] 数组对象的对象头必须有一块记录数组长度的数据(因为JVM可通过对象的元数据信息确定Java对象大小,但从数组的元数据中无法确定数组大小)(长度为JVM的一个字大小)。

后两者不是重点,也与本次主题无关,不再赘述。让我们来细究一下Mark Word的具体数据结构,及其在内存中的表现。

来,上图。

Java锁-Synchronized深层剖析

一般第一次看看这个图,都有点蒙,什么玩意儿啊,到底怎么理解啊。

所以这个时候需要我来给你举个简单例子。

如一个对象头是这样的:AAA..(一共23个A)..AAA BB CCCC D EE 。其中23个A表示线程ID,2位B表示Epoch,4位C表示对象的分代年龄,1位D表示该对象的锁是否为偏向锁,2位E表示锁标志位。

至于其它可能嘛。看到大佬已经写了一个表格,情况说明得挺好的,就拿来主义了。

在这里插入图片描述

图中展现了对象在无锁,偏向锁,轻量级锁,重量级锁,GC标记五种状态下的Mark Word的不同。

biased_lock lock 状态
0   01   无锁  
1   01   偏向锁  
0   00   轻量级锁  
0   10   重量级锁  
0   11   GC标记  

引用一下这位大佬的解释哈(毕竟大佬解释得蛮全面的,我就不手打了,只做补充)。

thread:持有偏向锁的线程ID。

epoch:偏向时间戳。

age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。

ptr_to_lock_record:指向栈中锁记录的指针。

ptr_to_heavyweight_monitor:指向管程Monitor的指针。

可能你看到这里,会对上面的解释产生一定的疑惑,什么是栈中锁记录,什么是Monitor。别急,接下来的Synchronized锁的实现就会应用到这些东西。

Java锁的内存实现

现在就让我们来看看我们平时使用的Java锁在JVM中到底是怎样的情况。

Synchronized锁一共有四种状态:无锁,偏向锁,轻量级锁,重量级锁。其中偏向锁与轻量级锁是由Java6提出,以优化Synchronized性能的(具体实现方式,后续可以看一下,有区别的)。

在此之前,我要简单申明一个定义,首先锁竞争的资源,我们称为“临界资源”(如:Synchronized(this)中指向的this对象)。而竞争锁的线程,我们称为锁的竞争者,获得锁的线程,我们称为锁的持有者。

无锁状态

就是对象不持有任何锁。其对象头中的mark word是

含义 identity_hashcode age biased_lock lock
示例   aaa...(25位bit)   xxxx(4位bit)   0(1位bit ,具体值:0)   01(2位bit ,具体值:01)    

无锁状态没什么太多说的。

这里简单说一下identity_hashcode的含义,25bit位的对象hash标识码,用于标识这是堆中哪个对象的对象头。具体会在后面的锁中应用到。

那么这个时候一个线程尝试获取该对象锁,会怎样呢?

偏向锁状态

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdpwz.html