Java对象头与锁

2020-06-02  本文已影响0人  抬头挺胸才算活着

参考资料:
https://stackoverflow.com/questions/26357186/what-is-in-java-object-header

普通对象

数组对象

其中 Mark Word 结构为

64 位虚拟机 Mark Word

偏向锁头部

开始头部的hashcode、age都为0、biased_lock都为1,所以是0x00..101
加锁之后前面的改为存放threadid,标志位还是01,不变
解锁之后还是存放着threadid

11:08:58.117 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
11:08:58.121 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101
11:08:58.121 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101

轻量级锁头部

开始头部的hashcode、age都为0、biased_lock都为0,所以是0x00..101
前面指向栈帧中的锁记录的地址,标志位改为00
退出锁之后跟进入之前是一样的

11:13:10.018 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
11:13:10.021 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000
11:13:10.021 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

调用对象 hashCode

调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销。
轻量级锁会在锁记录中记录 hashCode
重量级锁会在 Monitor 中记录 hashCode

因为调用了hashcode,所以加锁的是轻量级锁,跟上面轻量级锁是一样的。
轻量级锁会把hashcode等放在锁记录,所以解锁之后还是可以恢复hascode。

11:22:10.386 c.TestBiased [main] - 调用 hashCode:1778535015
11:22:10.391 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
11:22:10.393 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000
11:22:10.393 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001

当有其它线程使用偏向锁对象时(先后,不是同时),会将偏向锁升级为轻量级锁

t1加偏向锁(标志位为01),然后解锁,锁解完之后还是偏向于t1,看第二行前面threadid还是只想t2。
接着t2加锁,升级为轻量级锁(标志位为00)
解锁之后,markword变回hashcode那些。

[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

调用 wait/notify 锁变为重量级锁

第一行是加锁前,轻量级锁
第二行加锁之后,前面为threadid,t1进入wait
第三行t2调用notify
第四行t1变为重量级锁,头部指向monitor

[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101
[t2] - notify
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010

批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID。
当撤销偏向锁阈值超过 20 次后(这里不是针对同一个加锁对象,而是任何加锁对象),jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程。其中前19次都是,将偏向锁改为轻量级锁。

批量撤销

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的

上一篇 下一篇

猜你喜欢

热点阅读