synchronized在使用时需要传入一个对象
- 在非静态方法里需要传入的对象
this
- 在静态方法里需要传入的对象
类名.class
- 一个对象的实例,指向其实例的变量应当被
static
修饰。
synchronized是在锁什么
上锁就是改变对象的对象头
- 对象头是所有对象开头的公共部分。
- 对象头由两个词组成。
- 第一个词是MarkWord
- 第二个词是klass pointer,类的原数据的地址(Class Metadata Address),以此可以辨识一个类的实例用的是哪一个模板。
- 每个对象头都包括了堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本基本信息。
- 由此可以知道,对象的hashCode就存在对象的对象头里
- 加锁成功后会改变对象头的二进制码,记录同步状态。
- 进行垃圾回收调用重复算法时,重复状态也被保存在对象头里。
- 保存了一个指针,记录了一个类的实例属于哪个类。
进一步探讨对象的组成与大小来了解锁
- 对象在堆上要分配的内存不是固定的,所以会有内存溢出。
- JVM作为虚拟机规范,是如何约定对象头的
- 在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对象填充
如何考虑Java对象的大小
- 考虑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)对象头。
- 在64位虚拟机下,对象在声明并初始化一个boolean变量的情况下是16 byte。
-
布局分析
- 置于顶部的是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/64bit | Mark Word | 存储对象的hashCode、锁信息或分代年龄或GC标志等信息 |
32/64bit | klass 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)
- 在32 bit JVM下Mark Word是32 bit
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。
- unused:25