对象头
HotSpot虚拟机的对象头包括两部分信息:
- MarkWord
第一部分MarkWord,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为“MarkWord”。 - Class类型指针
对象头的另外一部分是Class类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例. - 数组长度(只有数组对象有)
如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度.
实例数据
实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
对齐填充
第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
对象头大小计算
要点
1. 在32位系统下,MarkWord是4字节,Class类型指针是4字节,所以对象头为8字节。
2. 在64位系统下,不开启压缩指针时,MarkWord是8字节,Class类型指针是8字节,所以对象头为16字节。
3. 64位开启指针压缩的情况下(压缩Class类型指针),MarkWord是8字节,Class类型指针是4字节,所以对象头为12字节。 数组长度4字节+数组对象头8字节(数组markword为4字节(64位未开启指针压缩的为8字节))+对象引用4字节(未开启指针压缩的64位为8字节)+对齐4字节=16字节。
4. 静态属性不算在对象大小内。
64位默认开启压缩指针默认开启,关闭采用JVM参数:-XX:UseCompressedOops。
查看对象头
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
import org.openjdk.jol.info.ClassLayout;
/**
* @Author: ZhangHao
* @Description: 对象头查看测试
* @Date: 2020/9/10 14:29
* @Version: 1.0
*/
public class Test {
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
}
}
结果:
java.lang.Object 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) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结果中低位字节靠左(小端存储方式)。00000001的01即对应锁标志位,01表示无锁。01前的0即是对应否偏向锁位,0表示非偏向锁。
监视器(Monitor)
(1)Monitor:
1.Monitor是一种用来实现同步的工具。
2.Monitor与每个java对象相关联,即每个java对象都有一个Monitor与之对应。
3.Monitor是实现Sychronized的基础。
(2)Monitor的基本结构:
1.Owner字段:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL。
2.EntryQ字段:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。
3.RcThis字段:表示blocked或waiting在该monitor record上的所有线程的个数。
4.Nest字段:用来实现重入锁的计数。
5.HashCode字段:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。
6.Candidate字段:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降;Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。
(3)Monitor与java对象以及线程如何关联:
可以参考:java对象堆内存结构
1.如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址。
2.Monitor的Owner字段会存放拥有相关联对象锁的线程id。
(4)Monitor实现:
1.Monitor是在jvm底层实现的,底层代码是c++。
2.Monitor的enter方法:获取锁。
3.Monitor的exit方法:释放锁。
4.Monitor的wait方法:为java的Object的wait方法提供支持。
5.Monitor的notify方法:为java的Object的notify方法提供支持。
6.Monitor的notifyAll方法:为java的Object的notifyAll方法提供支持。
synchronized
synchronized 是 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,而这段代码也被称为临界区。
synchronized 有多个叫法,而每个叫法都表明synchronized 的特性:
1、内置锁(也称隐式锁,监视器锁):synchronized 是内置于JDK中的,底层实现是native;同时,加锁、解锁都是JDK自动完成,不需要用户显式地控制,非常方便。
2、同步锁:synchronized 用于同步线程,使线程互斥地访问某段代码块、方法。这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
3、对象锁:准确来说,是分为对象锁、类锁。synchronized 以当前的某个对象为锁,线程必须通过互斥竞争拿到这把对象锁,从而才能访问 临界区的代码,访问结束后,就会释放锁,下一个线程才有可能获取锁来访问临界区(被锁住的代码区域)。synchronized锁 根据锁的范围分为 对象锁 和 类锁。对象锁,是以对象实例为锁,当多个线程共享访问这个对象实例下的临界区,只需要竞争这个对象实例便可,不同对象实例下的临界区是不用互斥访问;而类锁,则是以类的class对象为锁,这个锁下的临界区,所有线程都必须互斥访问,尽管是使用了不同的对象实例;