并发编程:Java对象头的信息分析

1. 对象的内存分布

在这里插入图片描述

  • 在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
  • 对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。
  • 对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个 java 数组,那么在对象头中还有一块用于记录数组长度的数据。
  • 第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot VM 的自动内存管理系统要求对对象的大小必须 是 8 字节的整数倍。当对象其他数据部分没有对齐时,就需要通过对齐填充来补全(比如对象头+实例数据大小为30,对齐填充自动变为2,此时对象大小变为32)。

1.1 创建一个maven工程

  • 引入对象分析jar包
    在这里插入图片描述
<dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.10</version>
</dependency>

1.2 创建一个空的实体类A

在这里插入图片描述

1.3 创建对象分析测试类HeadTest

在这里插入图片描述

package org.example.test;

import org.example.entity.A;
import org.openjdk.jol.info.ClassLayout;

public class HeadTest {

    public static void main(String[] args) {
        A a = new A();
        //parseInstance:解析实例,toPrintable:格式化打印
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }

}

1.4 查看打印

  • 对象头占12字节,对齐填充占4字节,整个对象大小为16字节,是8字节的整数倍。
  • 这里因为对象A内没有任何变量,为了满足对象大小为8的整数倍,所以有了4字节的对齐填充。
    在这里插入图片描述

1.5 为对象A添加一个整数型变量

在这里插入图片描述

1.6 查看打印

在这里插入图片描述

1.7 结论

  • 根据上面的结果可以认为一个对象的布局大体分为三个部分分别是
  • 对象头(Object header,占12字节)
  • 对象的实例数据
  • 对齐填充(非必须)

2. 对象头的规范

在这里插入图片描述

  1. type–类型指针
  2. GC state–gc分代年龄
  3. synchronization state–锁的状态
  4. identity hash code–一致性哈希码
  • 对象头Consists of two words

在这里插入图片描述

  1. mark word:包含锁状态,哈希码,gc状态位
  2. klass pointer:指向描述对象布局和行为的元对象,包含一个C++的虚拟表

在这里插入图片描述

  • 显示mark word的大小是64位,也就是8字节,所以klass pointer=12-8,也就是4字节
  • klass在开启指针压缩的情况下是4字节,在不开启指针压缩的情况下是8字节

在这里插入图片描述
在这里插入图片描述

3. HashCode

  • 计算对象的hashCode
  • hashCode的值与mark word内打印的结果刚好相反,因为intel的cpu使用的是小端存储,数据的高字节存储在高地址中,数据的低字节存储在低地址中 ;低字节先输入,高字节后输出,打印结果刚好相反。

在这里插入图片描述

  • 这里可以看出对象头内确实包含哈希码(前提是计算过hashcode)

4. 锁的状态

在这里插入图片描述

在这里插入图片描述

4.1 无锁不可偏向(有hashcode,001)

  • mark word的前25位没有使用,第26位到第56位存储哈希值,第57位没有使用,第58位到第61位存储gc分代年龄,第62位存储是否可偏向标识,第63位到第64位存储锁状态。
  • 计算对象的hashcode
    在这里插入图片描述
  • 此处gc分代年龄只占4位,也可以看出为什么分代年龄不能超过15,因为最大值是1111,对应十进制也就是15。

4.2 无锁可偏向(无hashcode,101),但是没有偏向(因为没加锁)

  • 关闭偏向延迟-XX:BiasedLockingStartupDelay=0
    在这里插入图片描述
  • 不计算对象的hashcode
    在这里插入图片描述

4.3 偏向锁已经偏向(101)

  • 不计算对象hashcode,并加锁
  • 在代码中大量使用锁的情况下,不要计算hashcode,可能会引起冲突
    在这里插入图片描述
  • 查看打印
    在这里插入图片描述
  • 如果这时候,计算了hashcode
    在这里插入图片描述
  • 查看打印
    在这里插入图片描述
  • 因为不可偏向,但是被加了锁,所以直接膨胀为轻量锁

4.4 轻量锁(00)

  • 创建HeadLockTest
    在这里插入图片描述
package org.example.test;

import org.example.entity.A;
import org.openjdk.jol.info.ClassLayout;

public class HeadLockTest {
    static A a = new A();
    static Thread t1;
    static Thread t2;

    /**
     * 线程第一次加锁,或者是同一个线程再次加锁--偏向锁
     * 交替执行--轻量锁
     * 资源竞争---mutex重量锁
     */
    public static void testLock(){
        synchronized (a){
            System.out.println("name:"+Thread.currentThread().getName());
            System.out.println(ClassLayout.parseInstance(a).toPrintable());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("线程还未启动----无锁");
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
        t1 = new Thread(){
            @Override
            public void run() {
                testLock();
            }
        };
        t2 = new Thread(){
            @Override
            public void run() {
                testLock();
            }
        };
        t1.setName("t1");
        t1.start();
        //阻塞,等待t1执行完后再启动t2
        t1.join();
        t2.setName("t2");
        t2.start();
    }

}
  • 关闭偏向延迟-XX:BiasedLockingStartupDelay=0
    在这里插入图片描述
  • t1与t2交替执行
    在这里插入图片描述
  • 查看打印
    在这里插入图片描述

4.5 重量锁(10)

  • 注释掉join方法,让t1与t2竞争同一把锁
    在这里插入图片描述

  • 查看打印
    在这里插入图片描述

  • 因为存在资源竞争,所以是重量锁,底层的同步或者锁机制是mutex。

5. 结论

在这里插入图片描述

  • 当一把锁第一次被线程持有是偏向锁,如果这个线程再次加锁还是偏向锁
  • 如果其他线程来加锁(交替执行),则膨胀为轻量锁
  • 如果发生资源竞争,则膨胀为重量锁
  • 过程不可逆(99%)

6. 偏向延迟

  • jdk6默认开启偏向锁,但是有4秒的延迟,所以启动后4秒内不会产生偏向锁;
  • 因为jvm内部使用了大量的锁,而jvm认为自己不会有偏向锁,至少都是轻量锁,为了减少偏向撤销带来的性能损耗,设置了4秒内不产生偏向锁,4秒后jvm已经启动成功,又不妨碍用户使用偏向锁。
  • 偏向锁设置参数
//关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
//禁止偏向锁
-XX:-UseBiasedLocking 
//启用偏向锁
-XX:+UseBiasedLocking 

在这里插入图片描述
在这里插入图片描述
代码下载地址https://gitee.com/fisher3652/lock-demo.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值