服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - 编程技术 - 面试官超级喜欢问的MarkWord

面试官超级喜欢问的MarkWord

2021-12-26 22:18程序员巴士七十一 编程技术

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

面试官超级喜欢问的MarkWord

前言

年底了,最近好几天没吃饭了,在微博吃瓜吃的饱饱的。

续上次被问到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分布

面试官超级喜欢问的MarkWord

64位虚拟机MarkWord分布

面试官超级喜欢问的MarkWord

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

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

面试官超级喜欢问的MarkWord

面试官: 我们怎么看对象头里的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. org.openjdk.jol
  3. jol-core
  4. 0.10

然后编写如下代码

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

打印如下

面试官超级喜欢问的MarkWord

markword在哪?Klass pointer在哪儿?

面试官超级喜欢问的MarkWord

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

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

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

面试官超级喜欢问的MarkWord

面试官: 对于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. 001 无锁状态 (第一位代表偏向标志,为0的时候表示不偏向,为1的时候表示偏向)
  7. 101 偏向锁 且记录线程ID
  8. 00 轻量锁 指向栈中锁记录的指针
  9. 10 重量级锁 重量级锁的指针
  10. 11 GC标志

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

面试官超级喜欢问的MarkWord

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

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

面试官超级喜欢问的MarkWord

阿巴阿巴: 程序启动后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. }

打印出来的数据如下

面试官超级喜欢问的MarkWord

阿巴阿巴: 让程序处于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. thread.start();
  9. // 等待thread运行完
  10. thread.join();
  11. synchronized (t) {
  12. System.out.println(ClassLayout.parseInstance(t).toPrintable());
  13. }
  14. }

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

面试官超级喜欢问的MarkWord

阿巴阿巴: 轻量锁的时候,虚拟机会在当前线程的栈帧中建立一个锁记录的空间“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. thread.start();
  9. // 等待thread运行完
  10. // thread.join(); 去掉该代码
  11. synchronized (t) {
  12. System.out.println(ClassLayout.parseInstance(t).toPrintable());
  13. }
  14. }

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

面试官超级喜欢问的MarkWord

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

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

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

阿巴阿巴: 好的~

原文链接:https://mp.weixin.qq.com/s/AexZotvQItGDcbty7tsxwg

延伸 · 阅读

精彩推荐
  • 编程技术真正聪明的程序员,总有办法不加班

    真正聪明的程序员,总有办法不加班

    工作效率提升了,就可以少加班了,聪明的程序员,总会有一堆可以提升编码效率的工具?当一种工具满足不了工作需求,就去探索新的,今天纬小创就给...

    今日头条12482021-03-04
  • 编程技术让开发效率倍增的 VS Code 插件

    让开发效率倍增的 VS Code 插件

    今天来分享一些提升开发效率的实用 VS Code 插件!Better Comments 扩展可以帮助我们在代码中创建更人性化的注释,有不同形式和颜色的注释供我们选择。 ...

    前端充电宝7132022-04-21
  • 编程技术简单、好懂的Svelte实现原理

    简单、好懂的Svelte实现原理

    本文会围绕一张流程图和两个Demo讲解,正确的食用方式是用电脑打开本文,跟着流程图、Demo一边看、一边敲、一边学...

    魔术师卡颂4822021-11-10
  • 编程技术AIOps,SRE工程师手中的利器

    AIOps,SRE工程师手中的利器

    AIOps开始成为一种极为重要的站点可靠性工程工具。它能够高效吸纳观察数据、参与数据以及来自第三方工具的数据,判断系统运行状态并保证其处于最佳...

    至顶网5972021-03-08
  • 编程技术用户态 Tcpdump 如何实现抓到内核网络包的?

    用户态 Tcpdump 如何实现抓到内核网络包的?

    在网络包的发送和接收过程中,绝大部分的工作都是在内核态完成的。那么问题来了,我们常用的运行在用户态的程序 tcpdump 是那如何实现抓到内核态的包...

    开发内功修炼11612021-09-08
  • 编程技术从Context源码实现谈React性能优化

    从Context源码实现谈React性能优化

    这篇文章主要介绍Context的实现原理,源码层面掌握React组件的render时机,从而写出高性能的React组件,源码层面了解shouldComponentUpdate、React.memo、PureComponen...

    魔术师卡颂5312020-12-20
  • 编程技术Delphi - Indy idMessage和idSMTP实现邮件的发送

    Delphi - Indy idMessage和idSMTP实现邮件的发送

    这篇文章主要介绍了Delphi - Indy idMessage和idSMTP实现邮件的发送,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...

    JJ_JeremyWu6592020-09-22
  • 编程技术2021年值得关注的React PDF 库

    2021年值得关注的React PDF 库

    今天,许多网络应用程序为其用户提供内置的PDF浏览选项。然而,选择一个并不容易,因为它们的功能远远超过显示PDF。在这篇文章中,我将评估5个React的...

    TianTianUp5232021-06-21