synchronize锁的膨胀及批量重偏向和撤销

 一个java对象包含:对象头,数据,对齐填充;

对象头包含:markword(如上图),类类型指针(klass word,如上图),legth(若是数组对象有这个值)

现在讲解下上图:

上图是jvm64位的对象头在各种锁状态下的信息;正常情况markword占64bit ;klass word 占64bit(一般默认开启指针压缩的:会压缩到32bit)

要分析对象头我们可以借助jol:
 <dependency>
<groupId>org.openjdk.jol</groupId>
 <artifactId>jol‐core</artifactId>
 <version>0.9</version>
 </dependency>

public class A{}

public class JOLExample1 {
   public static void main(String[] args) throws Exception {
     out.println(VM.current().details());
     out.println(ClassLayout.parseClass(A.class).toPrintable());
   }
 }

这样子输出后的结果如下:

 # Running 64‐bit HotSpot VM.
 # Using compressed oop with 0‐bit shift.
# Using compressed klass with 3‐bit shift.
 # Objects are 8 bytes aligned.
 # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
 # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

 OFFSET SIZE TYPE DESCRIPTION VALUE
 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
 8 4 (object header) 82 22 01 20 (10000010 00100010 00000001 00100000) (536945282)
 12 4 (loss due to the next object alignment)
 Instance size: 16 bytes
 Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

可以看出来'' (object header)"总共12byte ,也就是96bit,所以这里的指针被压缩过了;

分析一下这个(markword)64bit的意思:

 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

其中第一排括号里的前8bit表示: unsigned:0 ; age:0000 ; biased:0 ;lock:01

分别表示unsigned:没有使用,age:gc的分代年龄;biased:偏向锁的标志(0表示不是偏向锁,1表示是),lock:表示锁

这里的01表示无锁,00表示轻量锁,10表示重量锁;

下面这张图是32位的jvm:

64位的jvm和32的基本一致,后8位基本上是一样的,32位的时候无锁转态25bit表示hashcode值;

而64位是31bit表示hashcode值:如下图的括号里,从第一排第九个往下数31位表示hanscode值,剩下的25暂时没用到

 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

jvm启动时默认会使用偏向锁延迟(jvm估计认为,用synchronize的情况大多数都有竞争),延迟开启差不多在4s时间,当然可以配置jvm参数:

启用参数: 
-XX:+UseBiasedLocking
关闭延迟: 
-XX:BiasedLockingStartupDelay=0 
禁用参数: 
-XX:-UseBiasedLocking

偏向锁怎么膨胀到轻量级锁或者到重量锁呢?

 首先偏向锁(如果一个对象调用了,hashcode()方法,该对象就不可以是偏向锁了)是资源有且只有一个线程在竞争;(偏向一个线程后,基本上后面都会膨胀锁,不会再偏向另一个线程(批量重偏向除外))

偏向锁->轻量锁:多个线程,且是交替的执行,也就是一个线程获取到锁后,执行完同步块,这时候,另一个线程还处在自旋转态

去获取锁,且获取到了,总的来说是交替获取锁

偏向锁->重量锁:偏向锁还没执行完同步块代码,另一个线程自旋获取该锁,一直没获取到,挂起线程,这时候锁会升级重量锁;

(对象调用了wait()方法,则肯定会升级重量锁)

偏向锁在执行完同步代码块,锁不会撤销成无锁的情况;

轻量锁和重量锁在执行完同步代码块后,会执行锁撤销操作(挺耗性能毕竟要跟内核交互),变成无锁情况;

下面是demo:讲解了批量冲偏向和批量撤销

 public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        final List<A> listA = new ArrayList<A>();

        Thread t1 = new Thread() {
            public void run() {
                for (int i = 0; i < 100; i++)

                {
                    A a = new A();
                    synchronized (a) {
                        listA.add(a);
                    }
                }
                try

                {
                    Thread.sleep(100000000);
                } catch (
                        InterruptedException e) {
                    e.printStackTrace();
                }
            }

        };
        t1.start();
        Thread.sleep(3000);

        Thread t2 = new Thread() {
            public void run() {

                
                for (int i = 0; i < 40; i++) {
                    A a = listA.get(i);
                    synchronized (a) {
                    }
                }
                try {
                    Thread.sleep(20000);
                    A a = listA.get(88);
                    synchronized (a) {
                        out.println("打印list中第89个对象的对象头:");
                        out.println((ClassLayout.parseInstance(a).toPrintable()));
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t2.start();

        
        Thread.sleep(3000);
        out.println("打印list中第11个对象的对象头:");
        out.println((ClassLayout.parseInstance(listA.get(10)).toPrintable()));
        out.println("打印list中第26个对象的对象头:");
        out.println((ClassLayout.parseInstance(listA.get(25)).toPrintable()));
        out.println("打印list中第90个对象的对象头:");
        out.println((ClassLayout.parseInstance(listA.get(89)).toPrintable()));
        out.println("=============重新输出新实例1");
        out.println((ClassLayout.parseInstance(new A()).toPrintable()));

        Thread t3 = new Thread() {
            public void run() {

                for (int i = 20; i < 60; i++) {
                    A a = listA.get(i);
                    synchronized (a) {
                        if (i == 20 || i == 40 ||i == 52) {
                            out.println("thread3 第" + i + "次");
                            out.println((ClassLayout.parseInstance(a).toPrintable()));
                        }
                    }
                }
            }
        };
        t3.start();
        Thread.sleep(2000);
        out.println("重新输出线程1的状态--------------");
        out.println((ClassLayout.parseInstance(listA.get(98)).toPrintable()));
        Thread.sleep(2000);
        out.println("重新输出新实例2");
        out.println((ClassLayout.parseInstance(new A()).toPrintable()));
    }

A类就是一个普通的类,什么都没有,因为,jvm启用偏向锁延迟在4s左右,所以这里的demo没有设置参数,而是直接睡5s中,接下来看输出结果:

打印list中第11个对象的对象头:
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

打印list中第26个对象的对象头:
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 19 64 1a (00000101 00011001 01100100 00011010) (442767621)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

打印list中第90个对象的对象头:
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 64 1a (00000101 00000000 01100100 00011010) (442761221)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

=============重新输出新实例1
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 01 00 00 (00000101 00000001 00000000 00000000) (261)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

thread3 第20次
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 f3 d9 1b (10010000 11110011 11011001 00011011) (467268496)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

thread3 第40次
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 f3 d9 1b (10010000 11110011 11011001 00011011) (467268496)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

thread3 第52次
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 f3 d9 1b (10010000 11110011 11011001 00011011) (467268496)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

重新输出线程1的状态--------------
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 64 1a (00000101 00000000 01100100 00011010) (442761221)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

重新输出新实例2
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

打印list中第89个对象的对象头:
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           a8 ee 47 1b (10101000 11101110 01000111 00011011) (457698984)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           62 c2 00 20 (01100010 11000010 00000000 00100000) (536920674)
     12     4    int A.i                                       0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

首先介绍两个阈值:

intx BiasedLockingBulkRebiasThreshold   = 20   默认偏向锁批量重偏向阈值

intx BiasedLockingBulkRevokeThreshold  = 40   默认偏向锁批量撤销阈值

第一个阔值是重偏向:但我们创建一个类的时候这里是A.class .在其class会保存一个epoch值,假设是00,

1,new的每个对象它的对象头的epoch值,和class的一样,这例子中,先new了100个对象A.此时100个对象的线程都是偏向锁.

2,线程2去重写获取锁,这时候只有它本身竞争,所以这时候它会先进性(看该对象的epoch的值是否与对象的class里的epoch值是否相等,不相等的话,直接覆盖原有的偏向锁)cas操作(期望该对象头是没有锁的,若没有则,直接调用os的cas函数进行替换,cas函数在os系统中是一个指令所以是安全的), 例子中是该对象有线程1偏向锁,所以cas失败,这时候,就会进行锁撤销,撤销一次A.class的值会记录一次,当达到20次的时候,会改变class的epoch值变成01.

3.例子中线程2是循环拿了40个对象,这样前20会进行批量撤销操作,重新加锁为轻量锁,当该锁执行完同步块,会变成无所状态,20-40发生了重偏向所以其还是偏向锁只不过是另一个线程(这里的线程可能还是一样是因为jvm的线程复用情况,一种猜测),

4,所以打印的时候11输出的是无锁是因为,它本是轻量锁,退出了代码块会变成无锁,后面的26和90都是偏向锁,然后在打印一个重新new的对象(打印里的重新输出实例1),默认是偏向锁的状态,只是此时的锁不偏向任何线程.

5,当线程2执行完进入睡眠,这时候线程3执行,此时只有3竞争锁,所以它跟线程2一样进行上面第二部操作,当锁撤销到40次的时候,jvm会把该class认为是有问题的可能,这时候,该class创建的实力对象永远不可能是偏向锁了((打印里的重新输出实例2)),且重偏向也会无效了(打印里的第89个对象头);

批量重偏向和批量撤销介绍到这;下面看锁膨后的轻量锁和重量锁:

之前介绍了无锁的对象头和偏向锁的对象头的不同,接下来看轻量锁和重量锁

轻量锁的对象头,若是jvm32位的前30位保存lock_recod指针,若是64位前62保存指针,指向该持有线程的栈中(后续介绍)

重量锁的对象头,若是jvm32位的前30位保存指向堆的指针,若是64位前62保存指针,实现是用monitor函数(jvm的源码)(后续介绍)

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值