作者:京东零售 刘跃明
Monitor概念
Java对象的内存布局
对象除了我们自定义的一些属性外,还有其它数据,在内存中可以分为三个区域:对象头、实例数据、对齐填充,这三个区域组成起来才是一个完整的对象。
对象头:在JVM中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能,这些标记字段组成了对象头。
实例数据:存放类的属性数据信息,包括父类的属性信息。
对齐填充:由于虚拟机要求对象其实地址必须是8字节的整数倍,需要存在填充区域以满足8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。
图1
Java对象头
JVM中对象头的方式有以下两种(以32位虚拟机为例):
普通对象
Object Header (64 bits) | |
---|---|
Mark Word (32 bits) | Klass Word (32 bits) |
数组对象
Object Header (96 bits) | ||
---|---|---|
Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
Mark Word
这部分主要用来存储对象自身的运行数据,如hashcode、gc分带年龄等,Mark Word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark Word为32位,64位JVM为64位。为了让一个字大小存储更多的信息,JVM将字的最低两个位设置为标记位,不同标记位下的Mark Word示意如下:
Mark Word (32 bits) | State | ||||
---|---|---|---|---|---|
identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal | |
thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
ptr_to_lock_record:30 | lock:2 | LightweightLocked | |||
ptr_to_heavyweight_monitor:30 | lock:2 | HeavyweightLocked | |||
lock:2 | Marked for GC |
其中各部分的含义如下:
lock: 2位的锁状态标记位,该标记的值不同,整个Mark Word表示的含义不同。
biased_lock | lock | 状态 |
---|---|---|
0 | 01 | 无锁 |
1 | 01 | 偏向锁 |
0 | 00 | 轻量级锁 |
0 | 10 | 重量级锁 |
0 | 11 | GC标记 |
biased_lock: 对象是否启用偏向锁标记,只占1个二进制位,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age: 4位的Java对象年龄,在GC中,如果对象再Survivor区复制一次,年龄增加1,当对象达到设定的阈值时,将会晋升到老年代,默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6,由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
identity_hashcode: 25位的对象表示Hash码,采用延迟加载技术,调用方法System.idenHashcode()计算,并会将结果写到该对象头中,当对象被锁定时,该值会移动到管程Monitor中。
thread: 持有偏向锁的线程ID。
epoch: 偏向时间戳。
ptr_to_lock_record: 指向栈中锁记录的指针。
ptr_to_heavyweight_monitor: 指向管程Monitor的指针。
Klass Word
这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例,该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
array length
如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM长度为32位,64位JVM则为64位。
Monitor原理
Monitor被翻译为监视器或管程
每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针。
Monitor结构如下:
图2
?刚开始Monitor中Owner为null
?当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner
?在Thread-2上锁的过程中,如果Thread-3、Thread-4、Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED
?Thread-2执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争是非公平的,也就是先进并非先获取锁
?图2中WaitSet中的Thread-0、Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程,后面讲wait-notify时会分析
注意:
?synchronized必须是进入同一个对象的Monitor才有上述的效果
?不加synchronized的对象不会关联监视器,不遵从以上规则
synchronized原理
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
对应的字节码为:
public static main([Ljava/lang/String;)V
TRYCATCHBLOCK L0 L1 L2 null
TRYCATCHBLOCK L2 L3 L2 null
L4
LINENUMBER 6 L4
GETSTATIC MyClass03.lock : Ljava/lang/Object;
DUP
ASTORE 1
MONITORENTER //注释1
L0
LINENUMBER 7 L0
GETSTATIC MyClass03.counter : I
ICONST_1
IADD
PUTSTATIC MyClass03.counter : I
L5
LINENUMBER 8 L5
ALOAD 1
MONITOREXIT //注释2
L1
GOTO L6
L2
FRAME FULL [[Ljava/lang/String; java/lang/Object] [java/lang/Throwable]
ASTORE 2
ALOAD 1
MONITOREXIT //注释3
L3
ALOAD 2
ATHROW
L6
LINENUMBER 9 L6
FRAME CHOP 1
RETURN
L7
LOCALVARIABLE args [Ljava/lang/String; L4 L7 0
MAXSTACK = 2
MAXL