我们经常会说到synchronized是一把重量级的锁,难道是因为这把锁有几斤?我们也常听到说,在JDK1.6中对其进行了优化,那么又是怎么实现的?想了解这些,我们必须先详细的了解Java对象的结构。
一:Java对象,请掀起你的盖头来
以下是64位JVM下的对象结构描述:
对象结构
可以看到,在Java中,对象的结构主要分为:对象头,实例数据以及填充数据。需要强调的是,实例数据与填充数据是不一定要有的。如果一个对象中没有任何的属性实例,那么实例数据是可以为空的。而填充数据的有无则取决于对象头和实例数据的字节总长度,因为填空数据要保证整个对象的长度为8的整数倍,当对象头加上实例数据的字节总长度已经是8的整数倍了,那么填充数据为空。
对象头结构
mark-word :默认8个字节。
Klass Point :对象的类型指针,jdk1.8默认开启指针压缩后为4字节,当在JVM参数中关闭指针压缩(-XX:-UseCompressedOops)后,长度为8字节。
数组长度:当对象为数组的时候,才会有的结构,用来记录数组的长度信息。
对此,我们可以看出,在64位虚拟机中,一个对象头的长度最小为8字节(mark-word)+ 4字节(Klass Point,默认开启的指针压缩)= 12个字节。而对象的长度必须为8字节的整数倍,那么就必须还有4字节的填充数据,那么一个对象就最少有16个字节的长度。
Mard-Word
记录对象的HashCode,分代年龄,锁类型等信息,长度为8个字节。而synchronized锁在不同的场景下会进行相关的升级优化,其记录的锁的类型是在变化的。
相关锁的类型可分为:无锁,偏向锁,轻量锁,重量锁,以及GC标记,一共五种。当出现锁的相关竞争问题时,其锁类型会不断的进行升级优化,由偏向锁升级为轻量锁,轻量锁升级为重量锁。而2bit的话最多只能标记4种状态,所以这里又多加了1bit,用来标记是否为偏向锁(哈哈,就是这么的淳朴)。
二:Java对象,请显现出你的真身
我们可以通过工具可以分析下它的具体结构,maven地址如下:
org.openjdk.jol
jol-core
0.9
Object obj = new Object();
obj.hashCode(); //显示调用下
String classInfo = ClassLayout.parseInstance(obj).toPrintable();
System.out.println(classInfo);
通过ClassLayout分析出其具体结构如下:
我们可以看到这个对象一共是16个字节(最后一个偏移量12字节+自身的4个字节),其中对象头12个字节,还有4个字节的填充数据。
而在对象头的12个字节中,我们知道有8个字节是mark-word,另外的4个字节为Klass Point 类型指针(默认开启了指针压缩),那么我们通过虚拟机参数(-XX:-UseCompressedOops)关闭指针压缩再看下效果:
这个时候对象的结构竟然只剩下对象头信息了,mark-word还是8个字节未变(可与上图对比VALUE的取值),而类型指针Klass Point 由开启压缩时的4个字节变成了8字节。(这些叙述可以结合最开始的对象结构示意图来好好理解下,那张图也是画了很久的呀)
看完这些我们在看看VALUE取值是什么含义?而这个是涉及到synchronized锁优化的重点,因为它记录着锁类型的关键信息 以及 HashCode ,分代年龄等信息。
头节点的前8个字节表示mark-word,可以看到这里的new Object()的hashCode的值为0x7ea987ac,而截图中的Value中的取值也有对应的取数,只是‘倒’过来一样,那这是怎么回事?
这里就涉及到‘大端存储与小端存储’的区别:
Big-Endian:高位字节存放于内存的低地址端,低位字节存放于内存的高地址端
Little-Endian:低位字节存放于内存的低地址端,高位字节存放于内存的高地址端
所以其mark-word的结构如下:
可以看到,最后的3bit(1bit标识偏向锁,2bit描述锁的类型)是跟锁相关的,而synchronized的锁优化升级就是修改的这几位上的标识用来区分不同的锁,从而采取不同的策略来提升性能。而关于synchronized的锁优化升级的细节,我将在下文详细的解析。
阅读我的原文
更多的深度文章解析,你可以关注我的公众号 :南瓜小灯
希望能够共同的学习成长!!