一、Java对象内存布局
Hotspot虚拟机的java对象的内存由以下几部分:
(1)对象头(Mark word / Klass Pointer / 数组长度)
(2)实例数据
(3)对齐填充数据
1.1. 对象头
- 对象头中的Mark word:是用于存储对象自身运行时的数据,占用8字节。例如:hashcode、GC分代、锁状态标志、偏向锁线程ID、偏向时间戳等。
- 对象头中Klass Pointer:对象指向它的类的元素局的指针,虚拟机通过这个指针类确定这个对象是哪个类的实例,占用8字节。(数组,对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是从数组的元数据中无法确定数组的大小)
- 对象头中的 数组长度:如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。
64位JVM中,Mark word内存布局,占用64位空间,也就是8字节;
32位JVM中,Mark word内存布局,占用32位空间,也就是4字节;
1.2. 数据实例
就是类中定义的成员变量属性。
类型 | 占用内存(字节) |
boolean | 1 |
byte | 1 |
short | 2 |
char | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
数组引用 | 开启压缩就占用4 byte,关闭压缩就占用8 byte |
对象引用 | 开启压缩就占用4 byte,关闭压缩就占用8 byte |
reference类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes。
1.3.对齐填充数据
对齐填充并不是必然存在的,也没有特定的含义,仅仅骑着占位符的作用。
由于Hotspot虚拟机的自动内存管理系统要求对象的其实地址必须是 8字节的整数倍,也就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数,因此,对象实例数据部分没有对齐的时候,就需要通过对齐填充来补全。
1.4.指针压缩
对象占用的内存大小收到VM参数UseCompressedOops的影响。java运行参数上使用 -XX:+UseCompressedOops
就是开启指针压缩;使用 -XX:-UseCompressedOops
就是关闭指针压缩
1.4.1. 对对象头的影响
开启压缩后,对象头大小为12bytes(64位虚拟机)。
1.4.2. 对引用类型的影响
开启压缩就占用4 byte,关闭压缩就占用8 byte(64位虚拟机)
二、Monitor监听器
源码路径:…\src\share\vm\runtime\objectMonitor.hpp
ObjectMonitor() {
_header = NULL;
_count = 0; // 记录个数
_waiters = 0,
_recursions = 0; // 重入次数
_object = NULL; // 储存monitor关联对象
_owner = NULL; // 储存当前持有锁的线程ID
_WaitSet = NULL; // 等待池:通过调用 wait(),将当前线程变为阻塞状态放到等待池
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // 多线程竞争锁时的单向链表
FreeNext = NULL ;
_EntryList = NULL ; // 锁池:处于等待锁block状态的线程,会被加入到该列表,如果被调用了notify(),则会将当前线程从 _WaitSet 移到 _EntryList
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
重量锁竞争流程
假设T1
、T2
、T3
、T4
同时使用synchronize竞争锁时
-
T1
、T2
、T3
、T4
会先加入到_cxq
中参与竞争锁 -
T1
竞争到锁,则会修改该对象的对象头的_owner
,并将其他线程T2
、T3
、T4
进入到锁池_EntryList
- 当
T1
调用了wait()方法,会修改线程变为阻塞、释放对象锁,并将当前线程T1
加入到等待池_WaitSet
中,然后再将T2
、T3
、T4
加入到_cxq
中,进行下一轮锁的竞争 - 当
T1
已加入到等待池_WaitSet
中,然后T2
对对象锁调用notify() / notifyAll()唤醒等待时,则T1
会从等待池_WaitSet
转移到锁池_EntryList
,等待下一轮的锁竞争 - 当
T1
执行完成,准备释放锁,会移除_owner
,并将T2
、T3
、T4
从锁池_EntryList
转移到竞争队列_cxq
中,进行下一轮锁的竞争