使用场景
- 使用场景分为两种,一种是用于类,一种是用于对象;
- 类: 修饰静态方法、同步块指向类;修饰方法指向当前类,同步块指向指定的类
- 对象: 修饰非静态方法、同步块指向对象;修饰方法指向当前实例,同步块指向指定的对象
- 每个对象和类都有自己独立的监听器Monitor,jvm用Monitor实现Lock;比如HashTable.class 有一个Monitor,每个new HashTable() 也有自己Monitor;
- 监听器Monitor同一时间只能有一个线程持有,也就是说一个对象的某个同步方法或同步块被任意线程访问时,改对象的所有同步方法和同步块都会阻塞
锁的升级(膨胀)
- jdk6之后引入了偏向锁、轻量级锁,就有了4种状态(无锁、偏向锁、轻量级锁、重量级锁);
- 锁在被持有时才会膨胀改变对象头中状态,一旦释放后会状态重置
- 锁状态存储在对象头 (下图是32位系统的【对象头结构】)
-
无锁(禁用偏向)
对象头内容:偏向标志(0)、标志位(01)、hashCode(生成过hashCode的对象有值);
未开启偏向锁时创建的对象、生成过hashCode的对象、数组对象都是无锁(禁用偏向)状态
jvm默认开启偏向锁,但是延迟开启默认4秒,所以默认前4秒创建的对象不可偏向;可以通过 -XX:BiasedLockingStartupDelay=0改为零延时(ms)
禁用偏向的对象锁升级时直接到轻量级锁
调用Object.hashCode() 或System.identityHashCode()生成hashCode; PS:第一次调用时才会生成,后面再调用直接从对象中获取 -
偏向锁
对象头内容:偏向标志(1)、标志位(01)、偏向线程ID、时间戳;初始未偏向任何线程时不记录任何线程ID;
当某个线程尝试执行同步代码时,如果对象记录的线程ID与之相同直接执行,否则使用CAS修改线程ID,修改失败则升级为轻量级锁
偏向锁状态下,对象生成hashCode,会直接升级为重量级锁 -
轻量级锁
对象头内容:偏向标志(0)、标志位(00)、Lock Record的指针;
当某个线程尝试执行同步代码时,如果为无锁状态,创建Lock Record并copy mark word;然后用CAS更新Lock Record的指针,更新成功直接执行,否则判断是否是指向当前线程;如果是直接执行,否则自旋CAS更新Lock Record的指针
自旋到达指定次数或者再有一个线程来竞争锁就会升级到重量级锁
锁释放使用CAS将copy的mark word写回,并改写偏向标志、标志位 -
重量级锁
对象头内容:偏向标志(0)、标志位(10)、Monitor的指针;
膨胀至重量级锁时,会创建ObjectMonitor对象并copy mark word
持有锁的线程会阻塞其他线程,通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现
锁释放也是通过monitor对象,锁释放后会回写mark word -
升级过程(懒得画了,从网上找到一个图)
对象头的操作
如上【对象头结构】图,对象头分为MarkWord、Class Metadata;Class Metadata存储对象类型的指针(指向元空间的类对象),MarkWord不同状态存储不同内容,附上一张64位系统空间分配明细图(借用)
-
查看工具(org.openjdk.jol)
maven配置
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.8</version> </dependency>
使用 ClassLayout.parseInstance(object).toPrintable() 可以获取到对象头内容,如下图 -
打印内容
红框部分是存储值,当前对象是数组,第4行是数组长度,非数组对象只有3行
第3行是Class Metadata,jvm默认开启指针压缩,所以是32位,如果关闭则是64位;打印出来会多一行 3、4行
第1、2行是MarkWord内容,位置使用排序是从第2行开始,每行按组从右向左,每8位一组,如下图顺序
第8组的第7、8位是标志位,无锁、偏向锁下第6位是偏向标志;
第8组的第2~5位是分代年龄,第1位应该是cms使用(开启指针压缩时)
无锁状态且对象生成了hashCode,第4组的2~8位加上5、6、7组共31位存储hashCode
偏向锁状态且已偏向某线程,第7组的1~6位加上1~6组共54位存储线程ID,第7组的7、8位存储epoch