ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

盘一盘 synchronized (一)—— 从打印Java对象头说起

2021-05-17 14:33:04  阅读:258  来源: 互联网

标签:Java Thread synchronized 对象 lock thread1 println 一盘 偏向


Java对象头的组成

Java对象的对象头由 mark word 和  klass pointer 两部分组成,

mark word存储了同步状态、标识、hashcode、GC状态等等。

klass pointer存储对象的类型指针,该指针指向它的类元数据

值得注意的是,如果应用的对象过多,使用64位的指针将浪费大量内存。64位的JVM比32位的JVM多耗费50%的内存。

我们现在使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。

 

以64位操作系统为例,对象头存储内容图例。

|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |       
|--------------------------------------------------------------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |    GC
|--------------------------------------------------------------------------------------------------------------|

 

简单介绍一下各部分的含义

  • lock:  锁状态标记位,该标记的值不同,整个mark word表示的含义不同。
  • biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

  • age:Java GC标记位对象年龄。
  • identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。
  • thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。
  • epoch:偏向时间戳。
  • ptr_to_lock_record:指向栈中锁记录的指针。
  • ptr_to_heavyweight_monitor:指向线程Monitor的指针。

 

使用JOL工具类,打印对象头

使用maven的方式,添加jol依赖

<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.8</version>
</dependency>

创建一个对象A

public class A {
    boolean flag = false;
}

使用jol工具类输出A对象的对象头

public static void main(String[] args){
    A a = new A();
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}

看看输出结果

输出的第一行内容和锁状态内容对应

unused:1 | age:4 | biased_lock:1 | lock:2

     0           0000             0                01     代表A对象正处于无锁状态

第三行中表示的是被指针压缩为32位的klass pointer

第四行则是我们创建的A对象属性信息 1字节的boolean值

第五行则代表了对象的对齐字段 为了凑齐64位的对象,对齐字段占用了3个字节,24bit

 

偏向锁

public static void main(String[] args) throws InterruptedException {
    Thread.sleep(5000);
    A a = new A();
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}

 输出结果

刚开始使用这段代码我是震惊的,为什么睡眠了5s中就把活生生的A对象由无锁状态改变成为偏向锁了呢?别急,容我慢慢道来!

JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁。这个延时的时间大概为4s左右,具体时间因机器而异。当然我们也可以设置JVM参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁。

可能你又要问了,我这也没使用synchronized关键字呀,那不也应该是无锁么?怎么会是偏向锁呢?

仔细看一下偏向锁的组成,对照输出结果红色划线位置,你会发现占用 thread 和 epoch 的 位置的均为0,说明当前偏向锁并没有偏向任何线程。此时这个偏向锁正处于可偏向状态,准备好进行偏向了!你也可以理解为此时的偏向锁是一个特殊状态的无锁

 

大家可以看下面这张图理解一下对象头的状态的创建过程

再来看看这段代码,使用了synchronized关键字

public static void main(String[] args) throws InterruptedException {
    Thread.sleep(5000);
    A a = new A();
    synchronized (a){
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

轻量级锁

public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        A a = new A();

        Thread thread1= new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("thread1 locking");
                    out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向锁
                }
            }
        };
        thread1.start();
        thread1.join();
        Thread.sleep(10000);

        synchronized (a){
            out.println("main locking");
            out.println(ClassLayout.parseInstance(a).toPrintable());//轻量锁
        }
    }

thread1中依旧输出偏向锁,主线程获取对象A时,thread1虽然已经退出同步代码块,但主线程和thread1仍然为锁的交替竞争关系。故此时主线程输出结果为轻量级锁。

重量级锁

public static void main(String[] args) throws InterruptedException {
    Thread.sleep(5000);
    A a = new A();
    Thread thread1 = new Thread(){
        @Override
        public void run() {
            synchronized (a){
                System.out.println("thread1 locking");
                System.out.println(ClassLayout.parseInstance(a).toPrintable());
                try {
                    //让线程晚点儿死亡,造成锁的竞争
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    Thread thread2 = new Thread(){
        @Override
        public void run() {
            synchronized (a){
                System.out.println("thread2 locking");
                System.out.println(ClassLayout.parseInstance(a).toPrintable());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    thread1.start();
    thread2.start();
}

thread1 和 thread2 同时竞争对象a,此时输出结果为重量级锁

作者: 柠檬五个半

出处:https://www.cnblogs.com/LemonFive/p/11246086.html

版权:本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。

标签:Java,Thread,synchronized,对象,lock,thread1,println,一盘,偏向
来源: https://blog.csdn.net/meser88/article/details/105357035

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有