JVM MarkWord与Lock Record && 锁

MarkWord

翻译过来就是标记字,既然是标记,那么就肯定是用来记录一些信息,x86下4字节,x64下8字节,后面紧跟着class pointer,目前jvm基本都是x64,我们都知道在x86下地址是4字节大小,在x64下地址是8字节大小,但JAVA默认情况下开启了指针压缩,将8字节指针压缩成4字节,但是内存对齐机制导致无论如何,在x64下这俩加起来后占用的空间都是16字节。

还有OopPointer(就是类中还有类成员,比如String xxxx,这种对象的指针,这玩意不在Markword中,只是顺嘴提一句),同样默认开启了指针压缩,在x64下同样也是4字节。这和C中不同。

这里要说一句的是,GC回收的年龄为什么默认是15(JDK8 PSPO),因为在markword中age只有4bit,而4bit最大也只能是15。

关于MarkWord具体结构就不画个图了,网上有很多资料,虽然有些是x86有些x64,但是总的来说结构不变,无非是hashcode区域大小变化了。

Lock Record

LockRecord在获取轻量级锁时才会有用,有对象markword的拷贝(Displaced Mark Word),里面owner指向对象的markword,并将对象的markword的锁记录指针指向获得锁线程的LockRecord.。

偏向锁

默认情况下偏向锁开关是开启的,也可以手动关闭,MarkWord的最后2个bit存储着锁标志,0b01为无锁状态或偏向锁状态,0b00为轻量级锁,0b10为重量级锁,0b11为GC标记

无锁状态和偏向锁状态是相同的,因为仅2bit也没办法再区分了,那么如何区分他是否处于偏向锁呢?在倒数第三个bit就是做这个事的。为0b1的情况下,就是处于偏向锁状态了

偏向锁干的事就是为了保证线程重入的情况下,可以不触发CAS或Mutex锁,由MarkWord中的ThreadID判断是否是当前线程(比如递归调用),如果是那么跳过获取锁的过程。

偏向锁不会主动释放,也就是说它不会在工作完成后给你恢复ThreadID等等信息,只有在其他线程来竞争时,才会被释放。因为偏向锁主要做的事就是为了可重入。

轻量级锁

轻量级锁也就是自旋锁,利用CAS尝试获取锁。如果你确定某个方法同一时间确实会有一堆线程访问,而且工作时间还挺长,那么我建议直接用重量级锁,不要使用synchronized,因为在CAS过程中,CPU是不会进行线程切换的,这就导致CAS失败的情况下他会浪费CPU的分片时间,都用来干这个事了。
轻量级锁是在发现2个不同线程在竞争时才会由偏向锁升级为轻量级锁
说到轻量级锁就要说到LockRecord。这里重点说一下为什么需要LockRecord.

有些人可能会想,利用CAS交换ThreadID尝试获取自旋锁不就行了?还需要LockRecord做什么?上面说了,偏向锁是不会主动释放的,至于为什么不主动释放咱也不清楚,咱也不敢问,你无法通过ThreadID知道当前是否还在工作还是工作完成了。

LockRecord是线程私有的(TLS),说人话就是每个线程有自己的一份锁记录,在创建完锁记录的空间后,会将当前对象的MarkWord拷贝到锁记录中(Displaced
Mark Word)。这是为什么呢?

因为当前对象的MarkWord的结构会有所变化,不再是存着hashcode等信息,将会出现一个指向LockRecord的指针,指向锁记录
每个线程有自己的锁记录,那么指向哪个线程中的锁记录呢?

  • 加锁: 在拷贝完成后,首先会挂起持有偏向锁的线程,因为要进行尝试修改锁记录指针,MarkWord会有变化,所有线程会利用CAS尝试将MarkWord的锁记录指针改为指向自己(线程)的锁记录,然后lockrecord的owner指向对象的markword,修改成功的线程将获得轻量级锁。失败则线程升级为重量级锁。
  • 释放: 释放时会检查markword中的lockrecord指针是否指向自己(获得锁的线程lockrecord),使用原子的CAS将Displaced MarkWord替换回对象头,如果成功,则表示没有竞争发生,如果替换失败则升级为重量级锁。既然是CAS修改的指针为什么释放时要检查指针和markword呢?

上面说了,偏向锁是为了可重入性,并不解决竞争问题,轻量级锁是在有偏向锁的情况下尝试获取锁,最好的情况就是获取偏向锁的线程已经完成了工作,轻量级锁是第二个线程进来时获取的,那么在释放时优先考虑并没有第三个线程参与竞争,没有重量级锁,但是为了确保这个问题,释放时要检查lockrecord指针和Displaced
Mark Word是否与对象MarkWord相同。

重量级锁

重量级锁jvm也是使用native code,也就是系统中的互斥体了,Windows和Linux中都是mutex内核对象。

Windows中的Mutex
Windows的Mutex对象可以通过用户层API CreateMutex获取(真正的内核层操作需要驱动,获取的对象其实都是属于内核层的mutex,因为这是windows公开的API),mutex和event对象很像,都有一个触发状态,CreateMutex 第二个参数为是否被占用,也就是锁的初始状态,如果为false,说明没有被使用,处于触发状态。
创建后使用WaitForSingleObject来等待对象触发,当WaitForSingleObject等待的对象触发后将被设为未触发状态,其他没有等待到触发事件的线程就会被挂起,并且等待固定时长,这个时间是WaitForSingleObject第二个参数,当调用ReleaseMutex后,Mutex对象又会被设置为触发状态,其他线程就会获取到锁了。CloseHandle关闭内核对象句柄就会解除当前进程与Mutex的关系。不过Mutex不只是锁那么简单。
当没有在私有命名空间中(默认的命名空间)创建了一个名为"test"的互斥体却没有释放,其他进程在同样的命名空间下无法创建同名互斥体,也就是说它可以做到进程的互斥。

Linux中的Mutex

linux所有和多线程有关的API 基本都是pthread开头,说点无关紧要的玩意,事实上在linux中使用C写程序的情况下,基本很少有人使用线程,因为在linux中线程其实也可以看作进程,如果拿linux的线程和windows的线程比,linux的线程更像是伪线程。

初始化一个互斥体:pthread_mutex_init()
加锁:pthread_mutex_lock()/pthread_mutex_trylock()
释放:pthread_mutex_unlock()
注销互斥体:pthread_mutex_destory()

作者:没事干写博客玩

地址:JVM MarkWord与Lock Record && 锁

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值