锁信息存放:
java对象:对象头、实例数据、对齐填充
对齐填充不是必要的、由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍、所以他仅仅起着占位符的作用
对象头:Mark Word、指向类的指针、数组长度(只有数组对象才有)
指向类指针:是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,长度:32位JVM--32bit,64位JVM--64bit。
Mark Word:用于存储对象自身的允许时数据,如哈希码(HashCode)、GC分代年龄、锁标志位、
线程持有的锁、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键。
长度:32位JVM--32bit,64位JVM--64bit。
数组长度(只有数组对象才有):32位JVM、64位JVM--32bit。
锁的状态中Mark Word记录:
无锁状态:Mark Word记录: 对象的哈希码(HashCode)、GC分代年龄、是否偏向锁、锁标志位
偏向锁状态:Mark Word记录: 线程ID、Epoch(偏向锁的时间戳)、GC分代年龄、是否偏向锁、锁标志位
设计理念:实际发现,在每次的多线程中总是同一个线程A访问,为了提高性能,节省资源消耗,就产生了偏向锁,锁总是被第一个占用它的线程拥有,
每次线程进入同步代码块之前就会检测Mark Word的线程ID是否为线程A,如果是就会直接执行同步代码块。
偏向锁获取过程:
1.首先获取锁对象的Mark word判断是否为可偏向状态(Mark Work中偏向锁的标记是否为1)
2.如果是可偏向状态,则通过 CAS 操作,把当前线程的 ID 写入到 Mark Word
a.如果CAS成功,就会把Mark Word中锁标志改为‘01’,表示已经获得了锁对象的偏向锁,接着执行同步代码块。
b.如果CAS失败,说明有其他线程已经获得了偏向锁,这种情况说明当前锁存在竞争,需要撤销已获得偏向锁的线程,
并且把它持有的偏向锁升级/膨胀为轻量级锁。
3.如果是已偏向状态,需要检查 Mark Word 中存储的线程ID是否等于当前线程的线程ID
a) 如果相等,不需要再次获得锁,可直接执行同步代码块
b) 如果不相等,说明当前锁偏向于其他线程,需要撤销偏向锁并升级/膨胀到轻量级锁
偏向锁的撤销过程:偏向锁不会主动释放(撤销),只有遇到其他线程竞争时才会执行撤销,由于撤销需要知道当前持有该偏向锁的线程栈状态,
因此要等到safepoint时执行,此时持有该偏向锁的线程(A)有两种情况;
safepoint:可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,如果有需要,可以在这个位置暂停。
a.撤销----A线程已经退出同步代码块,或者已经不再存活,则直接撤销偏向锁,变成无锁状态
b.升级----A线程还在同步代码块中,则将A线程的偏向锁升级为轻量级锁,当前线程执行轻量级锁状态下的锁获取步骤
轻量级锁状态:Mark Word记录:指向栈中锁记录的指针、锁标志位
设计理念:在某个业务功能中,有可能会被并发访问,也有可能不会被并发访问,这个业务块就做成同步块。实际发现,
对这个同步块访问不是每次都是并发访问,有可能没有出现并发访问的情况,而且这种情况还很多,
为了提高性能,节省资源消耗,就设计成轻量级锁。
轻量级锁获取过程:
1.在进入同步代码块的时候,JVM会检测当前线程对象的锁状态
如果同步对象锁状态为无锁状态,JVM会在当前线程的栈中创建一个锁记录(Lock Record)的空间,
用于存储锁对象的Mark Word的拷贝(Displaced Mark Word),如果是处于偏向锁状态,则使用原来的锁记录。
2.拷贝之后,当前线程使用CAS操作,把当前线程对象Mark Word的锁标志为“00”
a.CAS操作成功,则将锁对象的Mark Word更新为指向栈中锁记录的指针,并将锁记录里的owner指针指向锁对象的mark word。
b.CAS操作失败,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录(Lock Record)。
i.如果是,表示锁重入;然后当前线程栈帧中增加一个锁记录第一部分(Displaced Mark Word)为null,
并指向Mark Word的锁对象,起到一个重入计数器的作用。
ii.如果否,表示有竞争,锁被别的线程获取到了,这个时候当前线程使用自旋(默认10次)来等待获取锁,
等待次数达到阈值仍未获取到锁,则升级为重量级锁
自旋:让一个线程去执行一个无意义的循环,用于消耗cpu,目的为了等待自旋结束后,重新去竞争锁,
避免在很短时间之内,对线程进行阻塞、唤醒操作,从而节省资源,提高程序运行的性能。
轻量级锁解锁过程:
1.通过CAS操作把尝试把线程栈帧中复制的锁记录中的(Displaced Mark Word)替换当前对象头的MarkWord(即还原对象头)
a.替换成功,则释放锁
b.替换失败,说明已经膨胀为重量级锁,则在执行完同步块释放锁同时唤醒被挂起的线程
重量级锁状态:Mark Word记录:指向互斥锁(重量级锁)的指针、锁标志位
重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被称为互斥锁。
每一个JAVA对象都会与一个监视器monitor关联,我们可以把它理解成为一把锁,当一个线程想要执行同步代码块时,该线程得先获取到锁对象对应的monitor。
当一个monitor被持有后,它将处于锁定状态。
重量级锁加锁过程:
1.调用omAlloc分配一个ObjectMonitor对象,把Mark Word锁标志置为‘10’,然后Mark Word存储指向ObjectMonitor对象的指针。
ObjectMonitor对象有两个队列和一个指针,每个需要获取锁的线程都包装成ObjectWaiter对象
2.多个线程同时执行同一段同步代码时,ObjectWaiter先进入_EntryList队列,当某个线程获取到对象的monitor以后进入_Owner区域,
并把monitor中的owner变量设置为当前线程同时monitor中的计数器count+1;
重量级锁释放过程:
1.若同步块中的线程调用wait()方法,则释放持有的monitor,owner遍历值为null,count-1,同时线程进入_WaitSet等待被唤醒
2.若当前同步块执行完毕,则也释放持有的monitor,owner遍历置为null,count-1
锁的优缺点: