Java对象内存布局 & 对象头Monitor

一、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字节;
Java对象内存布局 & 对象头Monitor_第1张图片
Java对象内存布局 & 对象头Monitor_第2张图片

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;
  }

重量锁竞争流程

假设T1T2T3T4 同时使用synchronize竞争锁时

  1. T1T2T3T4 会先加入到_cxq 中参与竞争锁
  2. T1竞争到锁,则会修改该对象的对象头的_owner,并将其他线程T2T3T4进入到锁池 _EntryList
  3. T1调用了wait()方法,会修改线程变为阻塞、释放对象锁,并将当前线程T1加入到等待池_WaitSet中,然后再将T2T3T4加入到_cxq中,进行下一轮锁的竞争
  4. T1已加入到等待池_WaitSet中,然后T2 对对象锁调用notify() / notifyAll()唤醒等待时,则T1会从等待池_WaitSet 转移到锁池 _EntryList,等待下一轮的锁竞争
  5. T1执行完成,准备释放锁,会移除_owner,并将T2T3T4从锁池 _EntryList转移到竞争队列_cxq中,进行下一轮锁的竞争

你可能感兴趣的:(java,jvm,hotspot,monitor)