文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

面试官超级喜欢问的MarkWord

2024-12-02 12:14

关注

续上次被问到synchronized锁后,面试官继续刁难阿巴阿巴,进而深入到对象头中相关的概念。

当场拿offer

面试官: 上次提到了synchronized锁,那你知道synchronized锁具体是怎么实现的吗?

阿巴阿巴: 在JDK版本1.5及之前的版本synchronized主要靠的是Monitor对象来完成,同步代码块使用的是monitorenter和monitorexit指令,而synchronized修饰方法靠的是ACC_SYNCHRONIZED标识,这些都是进入到内核态进行加锁的,然后将竞争锁失败的线程直接挂起,等待后面恢复。

阿巴阿巴: 在JDK1.6及之后的版本中,synchronized锁得到了优化,引入了自适应自旋锁、偏向锁、轻量锁,他们主要优化了锁在一定条件下的性能。避免了一上来就加重量级锁,等待锁的其他线程只能乖乖挂起,对cpu性能影响特别大。

阿巴阿巴: 在hotspot虚拟机中,对象头主要包括两部分 MarkWord和Klass Pointer。

MarkWord 对象标记字段,默认存储的是对象的HashCode,GC的分代年龄(2bit最大表示15)和锁的标志信息等。对于32位的虚拟机MarkWord占32bit,对于64位的虚拟机MarkWord占用64字节。

Klass Pointer Class 对象的类型指针,它指向对象对应的Class对象的内存地址。大小占4字节(指针压缩的情况下为4字节,未进行指针压缩则占8字节)。32位虚拟机MarkWord分布

64位虚拟机MarkWord分布

图片来源https://blog.csdn.net/weixin_40816843/article/details/120811181

查看虚拟机是多少位的可以使用:java -version

面试官: 我们怎么看对象头里的MarkWord数据呢?

阿巴阿巴: 可以看到在openJDK中关于MarkWord的描述,首先可以在Github上找到Open Jdk的源码

gitHub地址:https://github.com/openjdk/jdk

在IDE中打开并找到如下的位置

src/hotspot/share/oops/markWord.hpp

  1. // 查看虚拟机是多少位的可以使用:java -version   
  2. //  32 bits: 
  3. //  -------- 
  4. //            hash:25 ------------>| age:4  unused_gap:1  lock:2 (normal object) 
  5. // 
  6. //  64 bits: 
  7. //  -------- 
  8. //  unused:25 hash:31 -->| unused_gap:1  age:4  unused_gap:1  lock:2 (normal object) 

阿巴阿巴: 当然可以引入openjdk提供的jol-core,然后进行打印即可。

  1. // 在pom中引入 
  2.  
  3.   org.openjdk.jol 
  4.   jol-core 
  5.   0.10 
  6.  

然后编写如下代码

  1. public static void main(String[] args) { 
  2.      Test t = new Test(); 
  3.      System.out.println(ClassLayout.parseInstance(t).toPrintable()); 
  4.  } 

打印如下

markword在哪?Klass pointer在哪儿?

1处是MarkWord占用8Byte也就是64bit

2处是Klass Pointer占用了4Byte也就是32bit

klass pointer看起来是被压缩了,怎么确定是被压缩了呢?可以通过如下命令

面试官: 对于JDK1.6及以上版本,synchronized和MarkWord有啥关系嘛?

阿巴阿巴: 那关系可大了,可以看到在MarkWord中有2bit用来表示锁的标志位,代表着经过优化的synchronized锁不会直接上重量级锁,而是由偏向锁转为轻量锁,再由轻量锁转为重量级锁,一步一步膨胀的过程。

下面是2bit的锁标志位代表的含义

  1. //    [ptr             | 00]  locked      ptr points to real header on stack 
  2. //    [header          | 01]  unlocked    regular object header 
  3. //    [ptr             | 10]  monitor     inflated lock (header is wapped out
  4. //    [ptr             | 11]  marked      used to mark an object 
  5. //    [0 ............ 0| 00]  inflating   inflation in progress 
  6.  
  7. 001  无锁状态 (第一位代表偏向标志,为0的时候表示不偏向,为1的时候表示偏向) 
  8. 101  偏向锁 且记录线程ID 
  9. 00   轻量锁 指向栈中锁记录的指针 
  10. 10   重量级锁 重量级锁的指针 
  11. 11   GC标志 

然后再找到上图Value部分的数据,这两位是锁的标志位

面试官: 你刚不是说有一位是锁的偏向标志吗?在哪儿呢?

阿巴阿巴: 锁的偏向标志就在锁标志的前一位

阿巴阿巴: 程序启动后4s就会加偏向锁,只不过这个偏向锁没有偏向任何线程ID,也属于无锁状态

阿巴阿巴: 当应用处于单线程环境中时,这时候上的是偏向锁,在对象头中偏向标示显示为1,案例如下

  1. public static void main(String[] args) { 
  2.      Test t = new Test(); 
  3.      new Thread(()->{ 
  4.          synchronized (t) { 
  5.              System.out.println(ClassLayout.parseInstance(t).toPrintable()); 
  6.          } 
  7.      }).start(); 
  8.  } 

打印出来的数据如下

阿巴阿巴: 让程序处于2个线程交替进行竞争锁

  1. public static void main(String[] args) throws InterruptedException { 
  2.         Test t = new Test(); 
  3.         Thread thread = new Thread(()->{ 
  4.             synchronized (t) { 
  5.                 System.out.println(ClassLayout.parseInstance(t).toPrintable()); 
  6.             } 
  7.  
  8.         }); 
  9.         thread.start(); 
  10.         // 等待thread运行完 
  11.         thread.join(); 
  12.          
  13.         synchronized (t) { 
  14.             System.out.println(ClassLayout.parseInstance(t).toPrintable()); 
  15.         } 
  16.     } 

可以看到当main线程拿锁时已经膨胀为轻量锁了,锁的2bit标志为变成00了

阿巴阿巴: 轻量锁的时候,虚拟机会在当前线程的栈帧中建立一个锁记录的空间“Lock Record”,用于存储锁对象目前的MarkWord的拷贝,这一步采用CAS,如果成功了,那么与此同时,2bit的锁标记位会从“01”转变为“00”。这就是加轻量锁的过程。

阿巴阿巴: 之所以引入偏向锁,是为了解决在无多线程竞争环境下的轻量锁,轻量锁CAS多次的尝试也是对性能的损耗。相对于轻量锁而言,偏向锁值只需要进行一次CAS,这次CAS是用来设置线程ID的,设置成功后就代表获取锁了。轻量锁更适合于线程交替执行的场景,它们通过CAS自旋,避免了线程直接挂起以及挂起后的恢复过程,以此来降低CPU的损耗。

阿巴阿巴: 最后让我们看看加上重量锁后的MarkWord表现吧,先上代码

  1. public static void main(String[] args) throws InterruptedException { 
  2.         Test t = new Test(); 
  3.         Thread thread = new Thread(()->{ 
  4.             synchronized (t) { 
  5.                 System.out.println(ClassLayout.parseInstance(t).toPrintable()); 
  6.             } 
  7.  
  8.         }); 
  9.         thread.start(); 
  10.         // 等待thread运行完 
  11.         // thread.join(); 去掉该代码 
  12.  
  13.         synchronized (t) { 
  14.             System.out.println(ClassLayout.parseInstance(t).toPrintable()); 
  15.         } 
  16.     } 

控制台打印如下,发现已经加上重量锁了,锁的2bit标志为变成10了。

阿巴阿巴: 当轻量级锁升级成重量级锁时,Mark Word的锁标记位更新为10,Mark Word 将指向互斥量(重量级锁)。

阿巴阿巴: 以上就是关于synchronized和MarkWord的关系啦。

面试官: 理解的不错,明天来上班吧~

阿巴阿巴: 好的~

 

来源:程序员巴士内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯