首先首区分内核态以及用户态
将操作系统分级别 用户态:只能访问用户能够访问的指令 内核态:执行在内核空间的,能访问所有指令
JDK早期,synchronized叫做重量级锁,因为申请锁资源必须通过kernel,系统调用,由JVM用户态到OS内核态,都需要经过用户态到内核态的转换。
认识对象的内存布局
对象的内存布局:当我们new一个对象的时候它里面的内存是如何分布的? hotspot实现:
class T{
int m;
}
当我们new如上对象的过程中,放到堆内存里面布局是: 最上面8个字节的markword 4个字节的内存指针(class pointer 通过指针找这个对象是属于哪个类的) 4个字节的instance data 就是T 里的 int 的大小(看有多少个成员变量) 一个对象在hotspot中的实现要求8字节对齐(这个对象的大小务必是8的整数倍) 通过Java Object Layout来对如上描述做一个证明:
public class HelloJOL {
public static void main(String[] args) throws Exception{
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
输出如下结果:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
首先从零开始往后数四个字节 ,又从第四个字节开始往后数四个字节,这里一共是八个字节(是markword),后面从第八个字节往后数四个字节就是内存指针,通过后面的value e5 01 00 f8 就能找到Object.class ,由于此处只有12个字节不能达到8字节的对齐,所以后面在12个字节的位置往后再数四个字节(loss due to the next object alignment)完成对象的对齐。
下面回归正题将上面的注解注释掉,看看sychronized加上这段话是如何输出的:
发现前两行的markword不同,发现上锁的本质就是在hotspot中改变了markword里面的内容,让markword记录所上锁的内容(markword还记录了hashcode的信息以及GC的信息) 如果要查看markword记录的具体内容可以查看,markOop.hpp里的内容: 如上注释分为两种一种是32位系统的一种是64位系统的,64位对应如下图所示: 锁升级的步骤: 当我们首先new 一个普通对象出来,一旦给这个对象加上sychonized修饰的话,他会升级为偏向锁,如果竞争有点激烈,会升级为轻量级锁(也称自旋锁,无锁态),竞争再加剧则通过操作系统申请重量级锁。 偏向锁和轻量级锁属于用户空间锁,不需要和操作系统交互。 重量级锁是需要向内核申请。 偏向锁概念:哪个线程先来就偏向于哪个,没必要设置竞争机制,只是将当前线程的指针设置在markword里面(无竞争,效率高) 轻量级锁概念:当锁竞争较激烈时,会转为自旋锁,当两个线程同时竞争同一把锁的时候,会在两个线程各自的内部都存储一个LR(Lock Record),这两个线程会通过自旋的方式竞争的将他们各自的Lock Record改变到markword上,一旦一个线程竞争到锁了改变了markword那么另一个线程就会通过CAS自选的方式一直等待当前这个竞争到锁资源的线程释放锁(在用户空间等待) 重量级锁:不在用户空间,这个锁从操作系统中申请,markword里面记录的是Object Monitor,就是JVM空间写的一个C++对象,而这个对象去访问的时候需要经过操作系统。
sychronized是可重入锁,重入次数必须记录,因为要解锁几次必须得对应 如果是偏向锁: 偏向锁(以及轻量级锁)->线程栈->LR+1 过程如下:首先无锁态存储的是identity HashCode,变为偏向锁的时候hashcode存入到LR指向的 displacedmarkword,偏向锁重入一次就再生成一个LR,此时LR的指针指向的是一个null(不需要再记录了,因为我此时已经记录了),解锁的时候就依次弹出最后解锁。 重量级锁:会记录在Object Monitor的一个字段上。
为什么有自旋锁还需要重量级锁? 如果持有锁执行时间较长,如果等待的线程较多,都在自旋等待,自旋是消耗CPU资源的,如果等的时间长,或者自旋 线程多,CPU被大量消耗,重量级锁里面有队列WaitSet,将自旋的线程放进去,将线程冻结到WaitSet中使其不占用CPU资源,等待操作系统调度。 偏向锁是否一定比自选锁效率高? 不一定,在明确知道会有多线程竞争的情况下,偏向锁肯定会涉及锁撤销过程(耗时间),这时候直接使用自旋锁。 JVM启动过程,会有很多线程竞争(明确),所以默认情况启动时不打开偏向锁,过一段时间才打开,默认情况下偏向锁有个时延,默认4s,由于偏向锁启动后没有偏向于任何线程叫做匿名偏向。
自旋锁什么时候升级为重量级锁? 竞争加剧:有线程超过10次自旋 -XX:PreBlockSpin,或者自旋线程数超过CPU核数的一半,1.6之后,加入自适应自选,JVM控制升级重量级锁
|