synchronized与对象头之间的关系

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。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值