synchronized锁优化前传-Java对象结构解析

我们经常会说到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地址如下:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

  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的锁优化升级的细节,我将在下文详细的解析。

阅读我的原文

更多的深度文章解析,你可以关注我的公众号 :南瓜小灯

希望能够共同的学习成长!!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
优化是指在多线程编程中,通过改进的机制和使用方式来提高程序的性能和并发能力。synchronized关键字是Java中最常用的机制之一,它可以保证同一时间只有一个线程可以进入被synchronized修饰的代码块。下面是一些synchronized优化的方法: 1. 减小的粒度:如果在一个方法中有多个synchronized代码块,可以考虑将这些代码块拆分成多个方法,以减小的粒度。这样可以使得多个线程可以并发执行不同的代码块,提高程序的并发性能。 2. 使用局部变量替代成员变量:在使用synchronized关键字时,尽量使用局部变量而不是成员变量。因为成员变量的访问需要通过对象实例来进行,而局部变量的访问是线程私有的,不需要加。 3. 使用同步代码块代替同步方法:在某些情况下,使用同步代码块比使用同步方法更加灵活。同步代码块可以指定的粒度,只对需要同步的代码进行加,而不是整个方法。 4. 使用volatile关键字:volatile关键字可以保证变量的可见性和禁止指令重排序,可以在一定程度上替代synchronized关键字。但是需要注意,volatile关键字只能保证单个变量的原子性,不能保证多个操作的原子性。 5. 使用Lock接口:Java提供了Lock接口及其实现类ReentrantLock,相比于synchronized关键字,Lock接口提供了更加灵活的机制。可以手动控制的获取和释放,可以实现公平和非公平,并且支持多个条件变量。 6. 使用读写:如果在多线程环境下,读操作远远多于写操作,可以考虑使用读写ReadWriteLock来提高程序的并发性能。读写允许多个线程同时读取共享数据,但只允许一个线程写入共享数据。 7. 使用并发集合类:Java提供了一些并发集合类,ConcurrentHashMap、ConcurrentLinkedQueue等,它们内部使用了一些优化的技术,可以提高多线程环境下的并发性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值