2021年7月1日星期四

【转】谈谈 JVM 内部锁升级过程

一、加锁发生了什么

//System.out.println都加了锁public void println(String x) { synchronized (this) { print(x); newLine(); }}

简单加锁发生了什么?

要弄清楚加锁之后到底发生了什么需要看一下对象创建之后再内存中的布局是个什么样的?

一个对象在 new 出来之后在内存中主要分为 4 个部分:

  • Markword 这部分其实就是加锁的核心,同时还包含的对象的一些生命信息,例如是否 GC、进过了几次 Young GC 还存活等。
  • klass pointer 记录了指向对象的 class 文件指针。
  • instance data 记录了对象里面的变量数据。
  • padding 作为对齐使用,对象在 64 位服务器版本中,规定对象内存必须要能被 8 字节整除,如果不能整除,那么就靠对齐来补。举个例子:new 出了一个对象,内存只占用 18 字节,但是规定要能被 8 整除,所以 padding=6。

知道了这 4 个部分之后,我们来验证一下底层。借助于第三方包 JOL = Java Object Layout java 内存布局去看看。很简单的几行代码就可以看到内存布局的样式:

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core --><dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version></dependency>
public class JOLDemo { private static Object o; public static void main(String[] args) {  o = new Object();  synchronized (o){   System.out.println(ClassLayout.parseInstance(o).toPrintable());  } }}

将结果打印出来:

从输出结果看:

  1. 对象头包含了 12 个字节分为 3 行,其中前 2 行其实就是 Markword,第三行就是 klass 指针。值得注意的是在加锁前后输出从 001 变成了 000。Markword 用处:8 字节(64bit)的头记录一些信息,锁就是修改了 Markword 的内容 8 字节(64bit)的头记录一些信息,锁就是修改了markword的内容字节(64bit)的头记录一些信息。从 001 无锁状态,变成了 00 轻量级锁状态。

  2. new 出一个 object 对象,占用 16 个字节。对象头占用 12 字节,由于 Object 中没有额外的变量,所以 instance = 0,考虑要对象内存大小要被 8 字节整除,那么 padding=4,最 后 new Object() 内存大小为 16 字节。

二、锁的升级过程

2.1 锁的升级验证

探讨锁的升级之前,先做个实验。两份代码,不同之处在于一个中途让它睡了5秒,一个没睡。看看是否有区别。

public class JOLDemo { private static Object o; public static void main(String[] args) {  o = new Object();  synchronized (o){   System.out.println(ClassLayout.parseInstance(o).toPrintable());  } }}----------------------------------------------------------------------------------------------public class JOLDemo { private static Object o; public static void main(String[] args) {  try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }  o = new Object();  synchronized (o){   System.out.println(ClassLayout.parseInstance(o).toPrintable());  } }}

这两份代码会不会有什么区别?运行之后看看结果:

有点意思的是,让主线程睡了 5s 之后输出的内存布局跟没睡的输出结果居然不一样。Syn 锁升级之后,jdk1.8 版本的一个底层默认设置 4s 之后偏向锁开启。也就是说在 4s 内是没有开启偏向锁的,加了锁就直接升级为轻量级锁了。

那么这里就有几个问题了?

  • 为什么要进行锁升级,以前不是默认 syn 就是重量级锁么?要么不用要么就用别的不行么?
  • 既然 4s 内如果加了锁就直接到轻量级,那么能不能不要偏向锁,为什么要有偏向锁?
  • 为什么要设置 4s 之后开始偏向锁?

问题 1:为什么要进行锁升级?锁了就锁了,不就要加锁么?

首先明确 syn 锁 在 jdk1.2 之前效率非常低。那时候 syn 就是重量级锁,申请锁必须要经过操作系统老大 kernel 进行系统调用,入队进行排序操作,操作完之后再返回给用户态。

内核态:用户态如果要做一些比较危险的操作直接访问硬件,很容易把硬件搞死(格式化,访问网卡,访问内存干掉等),操作系统为了系统安全分成两层:用户态和内核态。申请锁资源的时候用户态要向操作系统老大内核态申请。Jdk1.2 的时候用户需要跟内核态申请锁,然后内核态还会给用户态。这个过程是非常消耗时间的,导致早期效率特别低。有些 jvm 就可以处理的为什么还交给操作系统做去呢?能不能把 jvm 就可以完成的锁操作拉取出来提升效率,所以也就有了锁优化。

问题 2:为什么要有偏向锁?

其实这本质上归根于一个概率问题,统计表示,在我们日常用的 syn 锁过程中 70%-80% 的情况下,一般都只有一个线程去拿锁,例如我们常使用的 System.out.println、StringBuffer,虽然底层加了 syn 锁,但是基本没有多线程竞争的情况。那么这种情况下,没有必要升级到轻量级锁级别了。

偏向的意义在于:第一个线程拿到锁,将自己的线程信息标记在锁上,下次进来就不需要在拿去拿锁验证了。如果超过 1 个线程去抢锁,那么偏向锁就会撤销,升级为轻量级锁,其实我认为严格意义上来讲偏向锁并不算一把真正的锁,因为只有一个线程去访问共享资源的时候才会有偏向锁这个情况。

问题 3:为什么 jdk8 要在 4s 后开启偏向锁?原文转载:http://www.shaoqun.com/a/839210.html

跨境电商:https://www.ikjzd.com/

虚拟信用卡:https://www.ikjzd.com/w/1055

kk馆:https://www.ikjzd.com/w/1713

好卖家:https://www.ikjzd.com/w/776


一、加锁发生了什么//System.out.println都加了锁publicvoidprintln(Stringx){synchronized(this){print(x);newLine();}}简单加锁发生了什么?要弄清楚加锁之后到底发生了什么需要看一下对象创建之后再内存中的布局是个什么样的?一个对象在new出来之后在内存中主要分为4个部分:Markword这部分其实就是加锁的核心,同时还包
美菜:https://www.ikjzd.com/w/1874
霸气!大疆回应美国增加关税的行为,只对美产品直接涨价!:https://www.ikjzd.com/articles/106944
为什么你赚不到钱?看看别人家的亚马逊运营!:https://www.ikjzd.com/articles/106946
免费工具推荐:独立站如何做好谷歌SEO排名?:https://www.ikjzd.com/articles/106947
2019亚马逊Q4即将来临,卖家要做好这些准备!:https://www.ikjzd.com/articles/106949
学长让我上课夹震动捧 少女思春上课偷偷夹笔杆:http://lady.shaoqun.com/a/247384.html
三个黑人一个接一个上 3人不停在她体内进进出出:http://lady.shaoqun.com/a/248389.html
把班主任玩到怀孕 老师奶头好大,下面好多水水:http://www.30bags.com/m/a/249901.html
男性应该经常做这些辅助动作:http://lady.shaoqun.com/a/394660.html
有钱的女人更容易找到伴侣?现实中的男人想要的不仅仅是爱:http://lady.shaoqun.com/a/394661.html
WISH首席财务官将辞去董事职务,新一任是谁?:https://www.ikjzd.com/articles/146287
亚马逊商家只会卖货不赚钱:https://www.ikjzd.com/articles/146274

没有评论:

发表评论