前言
在后续会深入分析 synchronized 的底层原理,但在说之前必须得讲解 Monitor 做位铺垫。
正文
Java 对象头
以 32 位 Java 虚拟机(JVM)为例子
普通对象 (64 位 或 8 个字节)
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
PS : Klass Word 可以理解为一个指针,指向了从属对象的 Class,找到它的类对象
数组对象(96位)
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
Mark Word 对象头结构
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:0 | lock:01| Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:01| Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:00| Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:10| Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:11| Marked for GC |
|-------------------------------------------------------|--------------------|
注意:32 位 JVM 中 Mark Word 是 32位,但在 64 位的 JVM 中,则 Mark Word 为 64 位。
State
-
Normal:正常状态 。
-
Lightweight Locked : 轻量级锁
-
Heavyweight Locked : 重量级锁
-
Marked for GC : 被垃圾回收
Mark Word
-
lock : 2位的锁状态标记位
-
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.identityHashCode() 计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程 Monitor 中。
-
thread :持有偏向锁的线程ID
-
epoch :偏向时间戳
-
ptr_to_lock_record :指向栈中锁记录的指针
-
ptr_to_heavyweight_monitor :指向管程 Monitor 的指针
举个例子,在 32 位 JVM 中,一个 Integer 要占 8 个字节作为对象头 + 4 个字节做为存储的Value的。
而一个 int 只需要 4 个字节即可。
也就是说,一个 Integer 比 int 大 3 倍,在内存敏感的地方,建议用 int 类型,而非包装类。
Monitor工作原理
Monitor 又称为监视器或管程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9iGP2xl3-1608306169220)(C:\Users\007\AppData\Roaming\Typora\typora-user-images\1608214001121.png)]
-
Monitor 是操作系统提供的,刚开始时,其中的 Owner 为 null
-
当 Thread-1 执行 synchronized(obj) 时会将 Monitor 的所有者 Owner 置为 Thread -1,Monitor 中只能有一个 Owner。
-
在 Thread -1 上锁的过程中,如果 Thread -3 、Thread -4 也执行 synchronized(obj) ,就会进入 EnrtyList BLOCK (阻塞)
-
Thread -1 执行完毕同步代码块内容后,接着就唤醒 EnrtyList 中等待的线程来竞争锁,竞争时时非公平的
-
WaitSet 中的 Thread-0、 Thread-2 是之前获得过锁,但条件不满足而进入 WAITING 状态的线程。
注意:
- synchronized 必须是进入同一个对象的 Monitor 才有上述效果
- 不加 synchronized 的对象不会关联监视器,不遵从以上规则
synchronized 原理
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock){
counter++;
}
}
IDEA 反编译后字节码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 // <- lock 引用(synchronized开始)
3: dup
4: astore_1 // lock 引用存储到临时变量 slot 1 中,为了后续解锁用
5: monitorenter // 将lock对象MarkWord 置为 Monitor 指针
6: getstatic #3 // <- i
9: iconst_1 // 准备常数 1
10: iadd // +1
11: putstatic #3 // -> i
14: aload_1 // <- lock 引用
15: monitorexit // 将lock对象重置,唤醒 EntryList
16: goto 24
19: astore_2 // e-> slot 2 将异常变量存储到临时变量
20: aload_1 // <- lock 引用 加载引用地址
21: monitorexit // 将 lock 对象 MarkWord 重置,唤醒 EntryList
22: aload_2 // <- slot 2(e) 把异常抛出去
23: athrow // throw e
24: getstatic #4 // Field
27: getstatic #3 // Field counter:I
30: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
33: return
Exception table:
from to target type
6 16 19 any
19 22 19 any