synchronized与对象头之间的关系

    技术2022-07-15  74

    synchronized在使用时需要传入一个对象

    在非静态方法里需要传入的对象 this 在静态方法里需要传入的对象 类名.class一个对象的实例,指向其实例的变量应当被static修饰。

    synchronized是在锁什么

    上锁就是改变对象的对象头
    对象头是所有对象开头的公共部分。对象头由两个词组成。 第一个词是MarkWord第二个词是klass pointer,类的原数据的地址(Class Metadata Address),以此可以辨识一个类的实例用的是哪一个模板。 每个对象头都包括了堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本基本信息。 由此可以知道,对象的hashCode就存在对象的对象头里加锁成功后会改变对象头的二进制码,记录同步状态。进行垃圾回收调用重复算法时,重复状态也被保存在对象头里。保存了一个指针,记录了一个类的实例属于哪个类。
    进一步探讨对象的组成与大小来了解锁
    对象在堆上要分配的内存不是固定的,所以会有内存溢出。JVM作为虚拟机规范,是如何约定对象头的 在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对象填充
    如何考虑Java对象的大小
    考虑Java的对象组成、对象的大小首先要考虑实例里面的实例属性(实例数据组成) Java对象的实例数据 身为强类型语言每个变量都要占字节,各占的不一样。不同的类因为变量的数量不同大小不一样。 要考虑对象头 对象头需要分配的内存是固定。 考虑数据对齐 64bit JVM 要求一个对象的大小必须是8的整数位。1byte的对象会被存成8byte
    通过示例来看Java对象的大小
    如下代码所示,创建一个对象,该对象在堆上。 考虑实例数据 boolean类型的变量会占一个字节。int类型的变量会占4个字节。 考虑数据对齐 64bit JVM 要求一个对象的大小必须是8的整数位。 public class L{ boolean flag = false; int test = 0; }
    打印对象布局
    导包 <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency> 代码 public class LockTest { boolean b=false; } static LockTest lockTest = new LockTest(); @Test public void testLock2() { String str = ClassLayout.parseInstance(lockTest).toPrintable(); System.out.println(str); }

    打印已经声明并初始化了一个布尔变量的类的结果

    res.LockTest0 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) 6b f0 fb 27 (01101011 11110000 11111011 00100111) (670822507) 12 1 boolean LockTest0.b false 13 3 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

    打印一个不包含任何属性、方法的类的结果

    res.LockTest1 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) 91 2c fc 27 (10010001 00101100 11111100 00100111) (670837905) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

    大小分析

    在64位虚拟机下,对象在声明并初始化一个boolean变量的情况下是16 byte。 此时前12 byte是(object)对象头。然后有1个byte是 boolean类型的变量。还有3个byte是填充 通过输出的Space losses: 0 bytes internal + 3 bytes external = 3 bytes total也可以看出。因为64bit JVM要求一个对象的大小必须是8的整数位,在对象头已经占12byte的情况下,有4位是填充,在boolean型变量占一个byte的情况下,就会有3个byte填充。由此可见对齐数据并不是固定存在的,需要填充的时候才存在. 在64位虚拟机下,对象在不包含任何属性、方法的情况下也是16 byte。 此时前12 byte是(object)对象头。

    布局分析

    置于顶部的是Java的对象头,它是实现synchronized的锁对象的基础,一般而言,synchronized使用的锁对象是存储在Java对象头里的。观察打印出来的图表,每一行都显示了4个字节,总共是12个字节.JVM使用两个字来存储对象头(如果对象是数组则会分配3个字,多出来的一个字记录的是数组长度),其主要结构是由Mark Word 和 kiass pointer(Class Metadata Address) 组成,其结构说明如下表。对象头分析,如下所示的对象头的二进制码,包含了各种信息:包括关于堆对象的布局、类型、GC状态、同步状态和标识哈希代码的基本信息。 注意: HashCode是地址,地址需要计算。HashCode需要计算得到,在内存中是不存在的。调用hashCode(),运行完后,在打印信息中的value区域的bit就会发生变化。 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) 91 2c fc 27 (10010001 00101100 11111100 00100111) (670837905)

    小结

    如果对象头加实例数据的大小无法是8的倍数,则会产生对齐数据,所以java的对象组成不是固定的。因为只有对象头无法是8的倍数,所以没有实例数据就只有对象头和实例数据。 虚拟机位数头对象结构说明32/64bitMark Word存储对象的hashCode、锁信息或分代年龄或GC标志等信息32/64bitklass pointer类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。 Mark Word与klass pointer的大小 在32 bit JVM下Mark Word是32 bit 前25bit存的是hashCode在紧接着4个bit是GC分段年龄在紧接着1个bit是偏向锁的信息。在紧接着2个bit存的是同步状态 hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object) JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) size:32 ----------------------------------------->|(CMS free block) PromotedObject*:29 --------->| promo_bits:3 ----->|(CMS promoted object) 在64 bit JVM下Mark Word的大小为64 bit 如果开启指针压缩,则klass pointer/Class Metadata Address是32 bit,此时对象头是96bit.不开启指针压缩的话klass pointer/Class Metadata Address是64 bit. unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2(normal object) JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2(biased object) PromotedObject*:61 ------------------>| Promo_bits:3------>|(CMS promoted object) size:64 -------------------------------------------------->|(CMS free block)
    Mark Word的内容是不固定的,根据对象的状态不同,里面存的信息也不同
    对象状态 无状态 指刚new出来的时候根据文档注释存的是unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2(normal object) 偏向锁 上锁后是偏向锁的状态,只有一个线程,只有这个对象。 轻量锁重量锁对象被gc标记 当对象不被引用时就会被gc标记

    以上状态对应的 Mark Word内容通过打印可以观测到

    图表
    |------------------------------------------------------------------------------------------------------| | Object Header (128 bits/96 bits) | |-----------------------------------------------------------------------------|------------------------| | Mark Word(64 bits) | Klass Word(64 bits) | |-----------------------------------------------------------------------------|------------------------| | unused:25 | identity_hashcode:31 | unsed:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | |-----------------------------------------------------------------------------|------------------------| | thread:54 | epoch:2 | unsed: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 | |-----------------------------------------------------------------------------|------------------------| 解读上表 unused:25 表示有25个字节没有被使用 identity_hashcode : 表示当前这个对象在内存当中的地址,简单的说就是对象的地址。 unused:1 表示紧接着还有一个字节没有被使用 age:4 表示有4位存的是年龄 biased_lock:1 表示偏向锁的偏向信息。 与lock联合表示对象的状态。 lock:2 这里有两位,存的是锁的信息。2位可以表示4种状态:00 01 11 10。
    Processed: 0.013, SQL: 9