synchronized源码解析
目录
- 对于可偏向、偏向锁、无锁、轻量锁、重量锁源码级解析
- 一、不同修饰的区别
- 1、修饰方法
- 2、修饰代码块
- 二、synchronized通用逻辑lock_object函数
- 1、biased_locking_enter函数-偏向锁
- 1.1、 无锁可偏向,怎么理解?
- 1.2、偏向锁
- 2、轻量|重量锁
- 函数lock_object
- lock_object小结:
- 函数 InterpreterRuntime::monitorenter
- monitorenter小结:
- 三、锁消除 monitorexit
- 总结
- 上锁:
- 偏向锁:
- 轻量锁 | 重量锁:
- 轻量锁:
- 重量锁:
- 解锁:
对于可偏向、偏向锁、无锁、轻量锁、重量锁源码级解析
我的环境 ubuntu 64 jdk1.8+ clion、gdb
这里简单介绍一下一些基础知识
1、锁状态位,markword或者_prototype_header的后三位,用org.openjdk.jol包查看就是高8位的第三位
2、其他位,先不管分代年龄和epoch那其他位就表示是偏向线程或上锁线程
--------------------------------正题开始(华丽丽得分割线)----------------------------------------------------
在分析锁synchronized前先要知道markword在jvm的类叫oopDesc。它存在两个地方,而且这两个地方指向的内存还不是同一块。其中一个就是我们俗称对象存,在oopDesc这个类中(oopDesc有很多继承类这里不展开),也就是我们常说的放在堆空间的对象;另一个就是klass(同样很多继承它的,不展开)存在元空间(klass-oop二分模型)。在oop中markOopDesc的变量名叫_mark,在kalss中变量名叫_prototype_header
typedef class markOopDesc* markOop;//oopDesc中的定义volatile markOop _mark;
//klass中的定义markOop _prototype_header; // Used when biased locking is both enabled and disabled for this type
一、不同修饰的区别
1、修饰方法
synchronized修饰为方法和代码块这两种申明创建的ObjectMonitor时机不同。下面的代码和汇编是修饰方法的。
//源码
public class Test{public static synchronized int exp(int t ) throws InterruptedException {return 0;}public static void main(String[] args) throws InterruptedException {exp(1);}
}//exp方法的字节码
Classfile /home/ziya/Desktop/Test.classLast modified 2022-12-17; size 394 bytesMD5 checksum ed0edf2dd2f862dbd54dd1820b813ed4Compiled from "Test.java"
public class Testminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref #4.#17 // java/lang/Object."<init>":()V#2 = Methodref #3.#18 // Test.exp:(I)I#3 = Class #19 // Test#4 = Class #20 // java/lang/Object#5 = Utf8 <init>#6 = Utf8 ()V#7 = Utf8 Code#8 = Utf8 LineNumberTable#9 = Utf8 exp#10 = Utf8 (I)I#11 = Utf8 Exceptions#12 = Class #21 // java/lang/InterruptedException#13 = Utf8 main#14 = Utf8 ([Ljava/lang/String;)V#15 = Utf8 SourceFile#16 = Utf8 Test.java#17 = NameAndType #5:#6 // "<init>":()V#18 = NameAndType #9:#10 // exp:(I)I#19 = Utf8 Test#20 = Utf8 java/lang/Object#21 = Utf8 java/lang/InterruptedException
{public Test();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 2: 0public static synchronized int exp(int) throws java.lang.InterruptedException;descriptor: (I)Iflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack=1, locals=1, args_size=10: iconst_01: ireturnLineNumberTable:line 5: 0Exceptions:throws java.lang.InterruptedExceptionpublic static void main(java.lang.String[]) throws java.lang.InterruptedException;descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=1, locals=1, args_size=10: iconst_11: invokestatic #2 // Method exp:(I)I4: pop5: returnLineNumberTable:line 9: 0line 10: 5Exceptions:throws java.lang.InterruptedException
}
修饰方法详细讲会有点冗长简单的说一下理解就行先上一段源码
address AbstractInterpreterGenerator::generate_method_entry(AbstractInterpreter::MethodKind kind) {bool synchronized = false;address entry_point = NULL;InterpreterGenerator* ig_this = (InterpreterGenerator*)this;switch (kind) {case Interpreter::zerolocals : /** zerolocals表示普通方法类型 **/ break;case Interpreter::zerolocals_synchronized: synchronized = true; /** zerolocals表示普通的、同步方法类型 **/ break;//.......................................省略好多代码.........................................}return ig_this->generate_normal_entry(synchronized);
}
jvm通执行java方法内的字节码前需要执行entry_point的执行流(可以理解为执行java方法前的通用逻辑(申请栈、栈帧,生成局部变量表等操作。具体的就不展开了),jvm在启动是时调用上面这段源码中的函数来构建entry_point执行流,entry_point执行流有很多种例如native_entry、abstract_entry。被synchronized修饰的普通方法这里只要关心上面源码中的两种。接着在generate_normal_entry函数中写入对应的硬编码,其中有一段
address InterpreterGenerator::generate_normal_entry(bool synchronized) {//.......................................省略好多代码..........................................if (synchronized) {lock_method();}//.......................................省略好多代码..........................................
}//
void InterpreterGenerator::lock_method(void) {// synchronize methodconst Address access_flags (rbx, Method::access_flags_offset());const Address monitor_block_top (rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize);const int entry_size = frame::interpreter_frame_monitor_size() * wordSize;
//...........................................省略一些ASSERT.................................................// get synchronization object{ Label done;const int mirror_offset = in_bytes(Klass::java_mirror_offset());__ movl(rax, access_flags);__ testl(rax, JVM_ACC_STATIC);__ movptr(rax, Address(rdi, Interpreter::local_offset_in_bytes(0))); // get receiver (assume this is frequent case)__ jcc(Assembler::zero, done);__ movptr(rax, Address(rbx, Method::const_offset()));__ movptr(rax, Address(rax, ConstMethod::constants_offset()));__ movptr(rax, Address(rax, ConstantPool::pool_holder_offset_in_bytes()));__ movptr(rax, Address(rax, mirror_offset));//...........................................省略一些ASSERT.................................................__ bind(done);}// add space for monitor & lock__ subptr(rsp, entry_size); // add space for a monitor entry__ movptr(monitor_block_top, rsp); // set new monitor block top__ movptr(Address(rsp, BasicObjectLock::obj_offset_in_bytes()), rax); // store object__ mov(rdx, rsp); // object address__ lock_object(rdx);
}
这就意味着如果使用的synchronized为true的entry_point必定先通过lock_method(void)函数里生成的对应的硬编码,那就解析一下lock_method这个函数生成的汇编
//lock_method对应的汇编
//...........................................省略一些ASSERT.................................................
//先找到java方法的access_flags demo的字节码中exp方法的修饰为
//flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED三个,对应jvm的三个值按位或计算位21
//#define JVM_ACC_PUBLIC 0x0001
//#define JVM_ACC_STATIC 0x0008
//#define JVM_ACC_SYNCHRONIZED 0x0020 mov 0x28(%rbx),%eax //__ movl(rax, access_flags); test $0x8,%eax //__ testl(rax, JVM_ACC_STATIC);是否仅为static方法mov (%r14),%rax //__ movptr(rax, Address(rdi, Interpreter::local_offset_in_bytes(0)))获取局部变量的一个参数je bind(done) //仅为static则跳转//从rbx寄存器中存的是Method指针mov 0x10(%rbx),%rax //__ movptr(rax, Address(rbx, Method::const_offset())) Method*+0x10指向ConstMethod*mov 0x8(%rax),%rax //__ movptr(rax, Address(rax, ConstMethod::constants_offset())) ConstMethod*+0x8指向ConstantPool*mov 0x20(%rax),%rax //__ movptr(rax, Address(rax, ConstantPool::pool_holder_offset_in_bytes())) ConstantPool*+0x20指向_pool_holder该变量是一个InstanceKlass指针,也就是当前常量池的持有类mov 0x70(%rax),%rax //__ movptr(rax, Address(rax, mirror_offset)) InstanceKlass*+0x70指当前java类class对应jvm的klass的中镜像oop _java_mirror
//...........................................省略一些ASSERT.................................................
// __ bind(done);sub $0x10,%rsp //提升16字节的栈空间存放lock record用mov %rsp,-0x40(%rbp) //将提升后的栈地址存入rbp - 0x40的位置 占用lock record中lock位置mov %rax,0x8(%rsp) //将当前类的oop也就是上面的oop _java_mirror存入,占用lock record中oop位置mov %rsp,%rsi //将lock record首地址暂存rsi寄存器class BasicObjectLock VALUE_OBJ_CLASS_SPEC {//lock record的原型private:BasicLock _lock; // the lock, must be double word alignedoop _obj; // object holds the lock; }
lock_method函数结束。总结一下,这里只变更了两个地方:rax寄存器,存着镜像对象oop;栈空间提升了0x10字节,存着lock record
接着分析lock_object(rdx)函数
2、修饰代码块
//字节码monitorenter的源码
void TemplateTable::monitorenter() {transition(atos, vtos);// check for NULL object/** cmp (%rax),%rax **/__ null_check(rax);const Address monitor_block_top(rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize);const Address monitor_block_bot(rbp, frame::interpreter_frame_initial_sp_offset * wordSize);const int entry_size = frame::interpreter_frame_monitor_size() * wordSize;Label allocated;// initialize entry pointer/** xor %esi,%esi **/__ xorl(c_rarg1, c_rarg1); // points to free slot or NULL 清零 释放rsi寄存器// find a free slot in the monitor block (result in c_rarg1){Label entry, loop, exit;/** mov -0x40(%rbp),%rcx **/__ movptr(c_rarg3, monitor_block_top); /** lea -0x40(%rbp),%rdx **/__ lea(c_rarg2, monitor_block_bot); // 获得lock record中ObjectMonitor起始地址
//跳转 /** jmp bind(entry) **/__ jmpb(entry);
/* | //这一段简单说就是循环对比多个lockrecord中的oop与当前要上锁的oop是否相同,找到当前要操作的lockrecord
/* | */ __ bind(loop); //跳这┐
/* | cmpq $0x0,0x8(%rcx) **/__ cmpptr(Address(c_rarg3, BasicObjectLock::obj_offset_in_bytes()), (int32_t) NULL_WORD); // |
/* | cmove %rcx,%rsi **/__ cmov(Assembler::equal, c_rarg1, c_rarg3); // |
/* | cmp 0x8(%rcx),%rax **/__ cmpptr(rax, Address(c_rarg3, BasicObjectLock::obj_offset_in_bytes())); // |
/* | je bind(exit) **/__ jccb(Assembler::equal, exit); // |
/* | add $0x10,%rcx **/__ addptr(c_rarg3, entry_size); // |
/* └跳到这 */ // |__ bind(entry); // |// check if bottom reachedv // |/** cmp %rdx,%rcx **/__ cmpptr(c_rarg3, c_rarg2); //通过mov[]和lea命令获得的值做对是否相同, // |/** rcx和rdx 不相同的场景在上面偏向锁的时候说解析过了**/ // |/** jne bind(loop) **/__ jcc(Assembler::notEqual, loop); //zf位不为0则跳到上面循环一下 // ┘__ bind(exit);}/** test %rsi,%rsi **/__ testptr(c_rarg1, c_rarg1); // rsi寄存器未经loop是0,下面jcc不跳转做上锁前准备。// 经过loop的rsi可能已经赋值一个oop,这里就得跳转了,重量锁上锁后解锁,不会回收oop中的ObjectMonitor指针/** jne 0x7fa2710458dc **/__ jcc(Assembler::notZero, allocated); // allocate one if there's no free slot{Label entry, loop,skip;//jvm原注释:1. compute new pointers 其实就是提升16字节的栈,当lock record用。-0x40(%rbp)就是lock record的首地址/** mov -0x40(%rbp),%rsi **/__ movptr(c_rarg1, monitor_block_bot); // c_rarg1: old expression stack bottom/** sub $0x10,%rsp **/__ subptr(rsp, entry_size); //rsp-0x10是开辟新的lockrecord/** sub $0x10,%rsi **/__ subptr(c_rarg1, entry_size); //rsi-0x10是根据已有lockrecord的地址再次开辟一个lockrecord/** mov %rsp,%rcx **/__ mov(c_rarg3, rsp); // set start value for copy loop/** mov %rsi,-0x40(%rbp) **/__ movptr(monitor_block_bot, c_rarg1); // set new monitor block bottom/** jmpq 0x7fa2710458d7 **/__ jmp(entry); //跳转到bind(entry)处//jvm原注释: 2. move expression stack contents 这一段是在有多个lockrecord的情况下,重新整理栈__ bind(loop);/** mov 0x10(%rcx),%rdx **/__ movptr(c_rarg2, Address(c_rarg3, entry_size)); //当前rcx指向栈顶,也就是lockrecord中的指向lock的栈指针/** mov %rdx,(%rcx) **/__ movptr(Address(c_rarg3, 0), c_rarg2); // and store it at new location/** add $0x8,%rcx **/__ addptr(c_rarg3, wordSize); // advance to next word__ bind(entry);/** cmp %rsi,%rcx **/__ cmpptr(c_rarg3, c_rarg1); // check if bottom reached/** jne 0x7fa2710458cc **/__ jcc(Assembler::notEqual, loop); // if not at bottom then// copy next word}// call run-time routine// c_rarg1: points to monitor entry__ bind(allocated);// Increment bcp to point to the next bytecode, so exception// handling for async. exceptions work correctly.// The object has already been poped from the stack, so the// expression stack looks correct./** inc %r13 **/__ increment(r13);// store object 先oop写入lockrecord/** mov %rax,0x8(%rsi) **/__ movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax);__ lock_object(c_rarg1);// check to make sure this monitor doesn't cause stack overflow after locking__ save_bcp(); // in case of exception__ generate_stack_overflow_check(0);// The bcp has already been incremented. Just need to dispatch to// next instruction.__ dispatch_next(vtos);
}
-------------------------------------------------------华丽得分割线-------------------------------------------------------------------
二、synchronized通用逻辑lock_object函数
无论是那种synchronized修饰,最终上锁的逻辑都是通过这个函数来操作的
void InterpreterMacroAssembler::lock_object(Register lock_reg) {if (UseHeavyMonitors) {call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), lock_reg);} else {Label done;const Register swap_reg = rax; // Must use rax, for cmpxchg instructionconst Register obj_reg = rcx; // Will contain the oopconst int obj_offset = BasicObjectLock::obj_offset_in_bytes();const int lock_offset = BasicObjectLock::lock_offset_in_bytes ();const int mark_offset = lock_offset + BasicLock::displaced_header_offset_in_bytes();Label slow_case;movptr(obj_reg, Address(lock_reg, obj_offset)); //mov 0x8(%rsi),%rcxif (UseBiasedLocking) {biased_locking_enter(lock_reg, obj_reg, swap_reg, noreg, false, done, &slow_case);}
............................................................................bind(done);}
}
1、biased_locking_enter函数-偏向锁
偏向锁有两种状态1、无锁可偏向和2、已有线程的偏向锁。
klass中_prototype_header的偏向在延迟偏向调度时,这个下面在细讲。这里看一下klass创建时初始化prototype_header
Klass::Klass() {.........................................................set_prototype_header(markOopDesc::prototype());
.........................................................
}
oopDesc的_mark是在new字节码中创建oop时分配的
//new字节码中定义oopDesc/** sub $0x10,%edx **/__ decrementl(rdx, sizeof(oopDesc)); //对象所需大小和对象头大小一样表明对象真正的实例数据内存为0,那么就不需要进行对象实例数据的初始化了,直接跳往对象头初始化处即可。/** je bind(initialize_header) **/__ jcc(Assembler::zero, initialize_header); //Hotspot中虽然对象头在内存中排在对象实例数据前,但是会先初始化对象实例数据,再初始化对象头。............................................................................/** mov %rcx,0x8(%rax,%rdx,8)**/__ movq(Address(rax, rdx, Address::times_8,sizeof(oopDesc) - oopSize),rcx);/** dec %edx **/__ decrementl(rdx);__ jcc(Assembler::notZero, loop);}// initialize object header only.__ bind(initialize_header);if (UseBiasedLocking) {//将类的偏向锁相关数据移动到对象头 需要注意的是这里的锁状态位是设置到堆区oop对象头的而不是klass的prototype_header中/** mov 0xb0(%rsi),%r10 **/__ movptr(rscratch1, Address(rsi, Klass::prototype_header_offset()));/** mov %r10,(%rax) **/__ movptr(Address(rax, oopDesc::mark_offset_in_bytes()), rscratch1);} else {//未开启偏向锁__ movptr(Address(rax, oopDesc::mark_offset_in_bytes()),(intptr_t) markOopDesc::prototype()); // header (address 0x1)}
所以说,jdk1.8下,通过openjdk.jol查看对象或者,在延迟偏移执行前无论是创建对象还是查看class,偏向锁位就是0,即无锁01状态
//偏向前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) df 03 00 f8 (11011111 00000011 00000000 11111000) (-134216737)
//偏向后0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) df 03 00 f8 (11011111 00000011 00000000 11111000) (-134216737)
1.1、 无锁可偏向,怎么理解?
最简单的说就是看markword布局百度一下一大把。101即偏向,准确的说thread指针按位或5(即二进制101)才是偏向锁,jvm的对象在初始时是01即无锁状态,启动时通过一个线程延迟调度将对象设置成101此时并没有java线程对对象进行加锁,何来偏向锁。最多只能说该对象是无锁,但是因为没上或者没上过锁,所以该对象是一个可以偏向某一线程的无锁对象。
//创建偏向的堆栈BiasedLocking::init biasedLocking.cpp:97Threads::create_vm thread.cpp:3663JNI_CreateJavaVM jni.cpp:5207InitializeJVM java.c:1214JavaMain java.c:376//设置偏向的线程堆栈enable_biased_locking biasedLocking.cpp:42Dictionary::classes_do dictionary.cpp:281SystemDictionary::classes_do systemDictionary.cpp:1764VM_EnableBiasedLocking::doit biasedLocking.cpp:57VM_Operation::evaluate vm_operations.cpp:62VMThread::evaluate_operation vmThread.cpp:377VMThread::loop vmThread.cpp:502VMThread::run vmThread.cpp:276java_start os_linux.cpp:828
看下源码
//biasedLocking.cpp
static void enable_biased_locking(Klass* k) {k->set_prototype_header(markOopDesc::biased_locking_prototype());
}
设置的内容:
可以看出只是设置了一个101
其他位并没有被使用。
1.2、偏向锁
来看看真正的偏向锁。为了方便将偏向延迟设置为0即-XX:BiasedLockingStartupDelay=0,依旧是上面那个demo,
下面是生成硬编码的源码,开始分析。在调用这个函数前lock_object函数中有一段 movptr(obj_reg, Address(lock_reg, obj_offset)); //mov 0x8(%rsi),%rcx意思的将lock record中的oop对象暂存到rcx寄存器中
void InterpreterMacroAssembler::lock_object(Register lock_reg) {............................................................................if (UseBiasedLocking) {biased_locking_enter(lock_reg, obj_reg, swap_reg, noreg, false, done, &slow_case);//这里已经解析过了}............................................................................
}int MacroAssembler::biased_locking_enter(Register lock_reg, // rsiRegister obj_reg, // rcxRegister swap_reg, // raxRegister tmp_reg, // r10bool swap_reg_contains_mark, // false rax不包括markword地址Label& done, // 跳转到锁结束的程序计数器处Label* slow_case, // 通过C 函数来上锁,当前及上面函数都是生成执行流汇编BiasedLockingCounters* counters) { LP64_ONLY( assert(tmp_reg != noreg, "tmp_reg must be supplied"); )bool need_tmp_reg = false;if (tmp_reg == noreg) {need_tmp_reg = true;tmp_reg = lock_reg;assert_different_registers(lock_reg, obj_reg, swap_reg);} else {assert_different_registers(lock_reg, obj_reg, swap_reg, tmp_reg);}Address mark_addr (obj_reg, oopDesc::mark_offset_in_bytes());Address saved_mark_addr(lock_reg, 0);Label cas_label;int null_check_offset = -1;if (!swap_reg_contains_mark) {null_check_offset = offset();movptr(swap_reg, mark_addr); // swap_reg_contains_mark为false则将oop中的mark_word传入rax寄存器 mov (%rcx),%rax}if (need_tmp_reg) {push(tmp_reg);}movptr(tmp_reg, swap_reg); // mov %rax,%r10 偏向操作以r10为操作单元andptr(tmp_reg, markOopDesc::biased_lock_mask_in_place);// and $0x7,%r10 只留后三位 cmpptr(tmp_reg, markOopDesc::biased_lock_pattern); // and $0x5,%r10 判断偏向锁位是否为1if (need_tmp_reg) {pop(tmp_reg);}jcc(Assembler::notEqual, cas_label); // 不可偏向直接跳转退出当前函数对应的执行流#ifndef _LP64movptr(saved_mark_addr, swap_reg);
#endifif (need_tmp_reg) {push(tmp_reg);}if (swap_reg_contains_mark) {null_check_offset = offset();}// mov 0x8(%rcx),%r10d 获取klass或压缩后的klass指针// shl $0x3,%r10 如果是压缩指针,则还原。具体的请看https://blog.csdn.net/ckiuick/article/details/126655121我的元空间解析// mov 0xb0(%r10),%r10 klass中的prototype_header,也就是klass的markOop load_prototype_header(tmp_reg, obj_reg);
#ifdef _LP64orptr(tmp_reg, r15_thread); //or %r15,%r10 设置偏向锁的偏向线程xorptr(tmp_reg, swap_reg); //xor %rax,%r10 将对象oop中的markword和r10中的prototype_header的按位异或Register header_reg = tmp_reg; //header_reg 指向r10
#else
//..................................32位逻辑不管.................................
#endif
//and指令会影响zf位,假设执行上面三段代码时rax中已是偏向锁,并且是当前程,则or %r15,%r10执行后r10中的立即数和rax的立即数是相等,经过xor %rax,%r10后,r10中的立即数后三位则为0,表示当前对象已经偏向当前线程了,and的时候zf为就变成1,jcc(Assembler::equal, done),jcc指令会跳转出去;而下方代码中的取反是为了排除分代年龄以免影响zf的结果andptr(header_reg, ~((int) markOopDesc::age_mask_in_place));
...............................................................................jcc(Assembler::equal, done);Label try_revoke_bias;Label try_rebias;
// 将header_reg和111相与,如果结果不为0,则表明header_reg后三位存在不为0的位,证明之前进行异或时,类的prototype_header后面三位与对象markOop的后三位不相等,但是能走到这,表明对象markword后三位为101,即偏向模式。现在类的prototype_header和对象markOop后三位不相等,即对象所属类不再支持偏向,发生了bulk_revoke,所以需要对当前对象进行偏向锁的撤销;否则表明目前该类还支持偏向锁,接着往下走。testptr(header_reg, markOopDesc::biased_lock_mask_in_place);jccb(Assembler::notZero, try_revoke_bias);// 测试对象所属类的prototype_header中epoch是否为0,不为0的话则表明之前异或时,类的prototype_header中epoch和对象markOop的epoch不相等,表明类在对象分配后发生过bulk_rebais()每次发生bulk_rebaise,类的prototype header中的epoch都会+1,所以之前对象的偏向就无效了,需要进行重偏向。否则接着往下走。testptr(header_reg, markOopDesc::epoch_mask_in_place);jccb(Assembler::notZero, try_rebias);NOT_LP64( movptr(swap_reg, saved_mark_addr); )andptr(swap_reg, // 取出对象markOop中除线程id之外的其他位markOopDesc::biased_lock_mask_in_place | markOopDesc::age_mask_in_place | markOopDesc::epoch_mask_in_place);if (need_tmp_reg) {push(tmp_reg);}
#ifdef _LP64movptr(tmp_reg, swap_reg); //将其他位和orptr(tmp_reg, r15_thread);//线程指针整合成一个markoop
#else
//..................................32位逻辑不管.................................
#endifif (os::is_MP()) {lock(); //汇编层面的lock}cmpxchgptr(tmp_reg, mark_addr); //cmpxchg %r10,(%rcx) rax和r10对比,相同设置到rcx中指针指向的内存,即oop中的markwordif (need_tmp_reg) {pop(tmp_reg);}if (counters != NULL) {cond_inc32(Assembler::zero,ExternalAddress((address) counters->anonymously_biased_lock_entry_count_addr()));}if (slow_case != NULL) {jcc(Assembler::notZero, *slow_case); // 如果cmpxchg未成功,需要走slow case}jmp(done);
//重偏向在上面“testptr(header_reg, markOopDesc::biased_lock_mask_in_place);”那段跳转bind(try_rebias);if (need_tmp_reg) {push(tmp_reg);}load_prototype_header(tmp_reg, obj_reg);
#ifdef _LP64orptr(tmp_reg, r15_thread);
#else
//..................................32位逻辑不管.................................
#endifif (os::is_MP()) {lock();}cmpxchgptr(tmp_reg, mark_addr); // compare tmp_reg and swap_regif (need_tmp_reg) {pop(tmp_reg);}if (counters != NULL) {cond_inc32(Assembler::zero,ExternalAddress((address) counters->rebiased_lock_entry_count_addr()));}if (slow_case != NULL) {jcc(Assembler::notZero, *slow_case);}jmp(done);//revoke在上面“testptr(header_reg, markOopDesc::epoch_mask_in_place);”那段跳转bind(try_revoke_bias);NOT_LP64( movptr(swap_reg, saved_mark_addr); )if (need_tmp_reg) {push(tmp_reg);}load_prototype_header(tmp_reg, obj_reg);if (os::is_MP()) {lock();}cmpxchgptr(tmp_reg, mark_addr); // compare tmp_reg and swap_regif (need_tmp_reg) {pop(tmp_reg);}if (counters != NULL) {cond_inc32(Assembler::zero,ExternalAddress((address) counters->revoked_lock_entry_count_addr()));}bind(cas_label);return null_check_offset;
}
//这两段中的逻辑和初次上偏向锁的基本一样不多述了
2、轻量|重量锁
为了方便先将偏向锁关闭
函数lock_object
//demo换一下
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;public class Test {public static Test syncTest = new Test();public static int exp(int t ) throws InterruptedException {synchronized(syncTest /**Test.class**/) {int i = 1;TimeUnit.SECONDS.sleep(1000000000);}return 0;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {exp(1);} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t2 = new Thread(() -> {try {exp(2);} catch (InterruptedException e) {throw new RuntimeException(e);}});t1.start();t2.start();new Scanner(System.in).nextByte();}}//exp方法的字节码public static int exp(int) throws java.lang.InterruptedException;descriptor: (I)Iflags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=4, args_size=10: getstatic #2 // Field syncTest:LTest;3: dup4: astore_15: monitorenter6: iconst_17: istore_28: getstatic #3 // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;11: ldc2_w #4 // long 1000000000l14: invokevirtual #6 // Method java/util/concurrent/TimeUnit.sleep:(J)V17: aload_118: monitorexit19: goto 2722: astore_323: aload_124: monitorexit25: aload_326: athrow27: iconst_028: ireturnException table:from to target type6 19 22 any22 25 22 anyLineNumberTable:line 9: 0line 10: 6line 11: 8line 12: 17line 14: 27StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 22locals = [ int, class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4Exceptions:throws java.lang.InterruptedException
void InterpreterMacroAssembler::lock_object(Register lock_reg) {if (UseHeavyMonitors) {call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), lock_reg);} else {Label done;const Register swap_reg = rax; // Must use rax, for cmpxchg instructionconst Register obj_reg = rcx; // Will contain the oopconst int obj_offset = BasicObjectLock::obj_offset_in_bytes();const int lock_offset = BasicObjectLock::lock_offset_in_bytes ();const int mark_offset = lock_offset + BasicLock::displaced_header_offset_in_bytes();Label slow_case;movptr(obj_reg, Address(lock_reg, obj_offset)); //mov 0x8(%rsi),%rcxif (UseBiasedLocking) {biased_locking_enter(lock_reg, obj_reg, swap_reg, noreg, false, done, &slow_case);}/** mov $0x1,%eax **/ movptr(swap_reg, (int32_t)1);//先加载一个无锁标识到rax/** or (%rcx),%rax **/ orptr(swap_reg, Address(obj_reg, 0));//和当前对象的对象的对象头 | 运算 01|01=1,01|00=01/** mov %rax,(%rsi) **/ movptr(Address(lock_reg, mark_offset), swap_reg); //先无锁头载入lockrecordassert(lock_offset == 0, "displached header must be first word in BasicObjectLock");if (os::is_MP()) {lock();//LOCK前缀确}/** rax无锁头和对象oop的markword对比 都是01则将rsi加载到rcx也就是oop中在前面monitorenter()函数中rsi寄存器存在的是基于当前os栈来开辟16字节大小的lockrecord monitorenter()函数lock record开辟代码段:mov -0x40(%rbp),%rsi __ movptr(c_rarg1, monitor_block_bot); // c_rarg1: old expression stack bottomsub $0x10,%rsp __ subptr(rsp, entry_size); //rsp-0x10是开辟新的lockrecordsub $0x10,%rsi __ subptr(c_rarg1, entry_size); //rsi-0x10是根据已有lockrecord的地址再次开辟一个**/ /** cmpxchg %rsi,(%rcx) **/ cmpxchgptr(lock_reg, Address(obj_reg, 0)); if (PrintBiasedLockingStatistics) { cond_inc32(Assembler::zero,ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));}//markword无锁则跳出当前函数 无论是否跳转,markword中都已存在当前lockrecord对应的os栈地址。//如果当前是首个线程上的轻量级锁,到这里上锁流程解锁,跳出当前函数jcc(Assembler::zero, done);//这里开始抢锁逻辑,进这里表示要走slow_case//jvm原话就是通过((mark - rsp) & (7 - os::vm_page_size()))这个算式测试markword中是不是一个明显的栈指针。//如果已有线程堆该对象上锁,markword指向的是lockrecord也就是上一个线程栈的地址//这里还有一种情况不会走slow_case,验证java方法有没有递归。递归的栈是同一线程的栈。//当递归时,第一次进入这里,rsi寄存器已经设置了rbp-0x40-0x10的位置,并被设置到oop的markword中//7 - os::vm_page_size()是取底12位值为0x007二进制是0000 0000 0111//那么0000 0000 0111 到 1111 1111 1000之间有4089个字节加上7正好4096个字节,假设每个栈使用128个字节//则前31次递归可以使用当前and指令将zf设置为1(64位栈是8字节对齐,所以后三位一定是000),//可以让下面的jcc(Assembler::zero, done)成立跳过slow_casesubptr(swap_reg, rsp); //递归举例:假设当前java方法需要栈大小为128,上次进入的栈地址为0x7FFFC497F440记录到markword,//二次(当次)进入时需要设置的便是0x7FFFC497F3C0,通过subptr(swap_reg, rsp)算出差值便是128//下面and就变成and 0xfffffffffffff007,80 0xfffffffffffff007的二进制是1111 ..中间48个1.. 0000 0000 0111//80的二进制是 0000 ..52个0.. 1000 0000 and后swap_reg变成0,zf置位1.jcc(Assembler::zero, done)成立跳转andptr(swap_reg, 7 - os::vm_page_size());//先保存到当前的lockrecord中movptr(Address(lock_reg, mark_offset), swap_reg);if (PrintBiasedLockingStatistics) {cond_inc32(Assembler::zero,ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));}jcc(Assembler::zero, done);bind(slow_case);call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), lock_reg);//进入slow_case,InterpreterRuntime::monitorenter函数bind(done);}
}
lock_object小结:
这段主要使用了三个寄存器,rax:临时锁头,rsi:指向lockrecord,rcx:当前对象的oop。
1、先设置无锁头到rax并异或markword得到一个oop的是已经上锁的值
2、将rax和rcx中的markword对比,如果都是无锁则通过cmpxchg指令将rsi中栈指针放入rcx的oop中,跳出函数执行下一个字节码。如果cmpxchg失败,做执行slow_case前的准备
3、cmpxchg失败则需要抢锁,走slow_case,进入 InterpreterRuntime::monitorenter函数进行膨胀
函数 InterpreterRuntime::monitorenter
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
.........................................................................................Handle h_obj(thread, elem->obj());if (UseBiasedLocking) {// Retry fast entry if bias is revoked to avoid unnecessary inflationObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);} else {ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);}
..........................................................................................
IRT_ENDvoid ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {markOop mark = obj->mark();if (mark->is_neutral()) {//无锁状态进入lock->set_displaced_header(mark);if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {//这节cas到markwordTEVENT (slow_enter: release stacklock) ;return ;}// Fall through to inflate() ...} elseif (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {//有锁,且锁是当前线程的lock->set_displaced_header(NULL);return;}
#if 0
.................................................................................
#endif
//markOopDesc::unused_mark()值为3,大概意思从这里开始oop中的markword并不会设置到lockrecord中lock的markOop中
//所以是lockrecord中的标记并不重要,只要看上去不=像重入锁或已上锁就行lock->set_displaced_header(markOopDesc::unused_mark());ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}//锁膨胀
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {// Inflate mutates the heap ...// Relaxing assertion for bug 6320749.assert (Universe::verify_in_progress() ||!SafepointSynchronize::is_at_safepoint(), "invariant") ;for (;;) {const markOop mark = object->mark() ;assert (!mark->has_bias_pattern(), "invariant") ;if (mark->has_monitor()) { //markword最后两位是否为10重量锁ObjectMonitor * inf = mark->monitor() ;assert (inf->header()->is_neutral(), "invariant");assert (inf->object() == object, "invariant") ;assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");return inf ;}if (mark == markOopDesc::INFLATING()) { //当前对象markword锁是否正在像重量锁膨胀TEVENT (Inflate: spin while INFLATING) ;ReadStableMark(object) ;continue ;}if (mark->has_locker()) { //当前对象markword按位& 3后是否为0即轻量级锁ObjectMonitor * m = omAlloc (Self) ; //申请Monitor对象m->Recycle();m->_Responsible = NULL ;m->OwnerIsThread = 0 ;m->_recursions = 0 ;m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/classmarkOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ; //设置0,锁的膨胀中状态if (cmp != mark) {//或许是防止多线程cmpxchg跟换markwordomRelease (Self, m, true) ;continue ; // Interference -- just retry}markOop dmw = mark->displaced_mark_helper() ;assert (dmw->is_neutral(), "invariant") ;// Setup monitor fields to proper values -- prepare the monitorm->set_header(dmw) ; //设置无锁头m->set_owner(mark->locker()); //设置当前线程栈(也就是属于哪个线程)m->set_object(object);//设置当前对象的oopguarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;//将当前的ObjectMonitor指正按位或2,设置到object(oop)的markword(重要)object->release_set_mark(markOopDesc::encode(m)); //....................................pref相关,省略......................................return m ;}void ATTR ObjectMonitor::enter(TRAPS) {Thread * const Self = THREAD ;void * cur ;//_owner:拥有锁的线程指针,如果当前对象已经是轻量锁则_owner是拥有锁的线程的线程栈地址即lockrecord对象//Self :当前执行的线程即要竞争上锁的线程对象指针//cur :cmpxchg设置成功则cur指向当前执行线程的指针,当前拥有锁的线程指针(或线程栈地址)//如果_owner属性是NULL就将其设置为Self,否则返回当前的_owner属性cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;if (cur == NULL) {return ;}if (cur == Self) {_recursions ++ ;//重入return ;}//轻量锁膨胀成重量级锁时,将owner设置为lock属性//在上面cmpxchg函数设置cur时cur指向了拥有锁的线程栈地址//Self->is_lock_owned函数是用当前线程的栈顶和栈底范围与cur对比,如果cur是在这一段栈范围内的//表示重入(嵌套java方法的中的syncchronized),将_recursions置为1后可直接返回if (Self->is_lock_owned ((address)cur)) {assert (_recursions == 0, "internal state error");_recursions = 1 ;// Commute owner from a thread-specific on-stack BasicLockObject address to// a full-fledged "Thread *"._owner = Self ;OwnerIsThread = 1 ;return ;}// We've encountered genuine contention.assert (Self->_Stalled == 0, "invariant") ;Self->_Stalled = intptr_t(this) ;if (Knob_SpinEarly && TrySpin (Self) > 0) { //Knob_SpinEarly默认为1,TrySpin 自旋assert (_owner == Self , "invariant") ;assert (_recursions == 0 , "invariant") ;assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;Self->_Stalled = 0 ;return ;}
.............................................省略.........................................................for (;;) {jt->set_suspend_equivalent();EnterI (THREAD) ;if (!ExitSuspendEquivalent(jt)) break ;_recursions = 0 ;_succ = NULL ;exit (false, Self) ;jt->java_suspend_self();}Self->set_current_pending_monitor(NULL);}Atomic::dec_ptr(&_count);assert (_count >= 0, "invariant") ;Self->_Stalled = 0 ;
.............................................省略........................................................}void ATTR ObjectMonitor::EnterI (TRAPS) {Thread * Self = THREAD ;assert (Self->is_Java_thread(), "invariant") ;assert (((JavaThread *) Self)->thread_state() == _thread_blocked , "invariant") ;// Try the lock - TATASif (TryLock (Self) > 0) {assert (_succ != Self , "invariant") ;assert (_owner == Self , "invariant") ;assert (_Responsible != Self , "invariant") ;return ;}DeferredInitialize () ;//再次尝试自旋,获取锁成功则返回if (TrySpin (Self) > 0) {assert (_owner == Self , "invariant") ;assert (_succ != Self , "invariant") ;assert (_Responsible != Self , "invariant") ;return ;}// The Spin failed -- Enqueue and park the thread ...assert (_succ != Self , "invariant") ;assert (_owner != Self , "invariant") ;assert (_Responsible != Self , "invariant") ;ObjectWaiter node(Self) ;Self->_ParkEvent->reset() ;node._prev = (ObjectWaiter *) 0xBAD ;node.TState = ObjectWaiter::TS_CXQ ;ObjectWaiter * nxt ;for (;;) {node._next = nxt = _cxq ;if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ; //放入cxq队列头// Interference - the CAS failed because _cxq changed. Just retry.// As an optional optimization we retry the lock.if (TryLock (Self) > 0) {assert (_succ != Self , "invariant") ;assert (_owner == Self , "invariant") ;assert (_Responsible != Self , "invariant") ;return ;}}if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {//nxt和_EntryList为NULL,标识当前线程是第一个阻塞的线程,则将_Responsible设置为当前线程对象的指针Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;}TEVENT (Inflated enter - Contention) ;int nWakeups = 0 ;int RecheckInterval = 1 ;int idx = 0;for (;;) {if (TryLock (Self) > 0) break ;assert (_owner != Self, "invariant") ;if ((SyncFlags & 2) && _Responsible == NULL) {//_Responsible将目标线程置为当前线程对象的指针Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;}// park selfif (_Responsible == Self || (SyncFlags & 1)) {TEVENT (Inflated enter - park TIMED) ;Self->_ParkEvent->park ((jlong) RecheckInterval) ;//当前线程park// Increase the RecheckInterval, but clamp the value.RecheckInterval *= 8 ;if (RecheckInterval > 1000) RecheckInterval = 1000 ;} else {//当前线程不是第一个阻塞的线程,在上面一个循环中已经将当前线程加入cxq,这里可以直接park等待解锁唤醒TEVENT (Inflated enter - park UNTIMED) ;Self->_ParkEvent->park() ;}if (TryLock(Self) > 0) break ;TEVENT (Inflated enter - Futile wakeup) ;if (ObjectMonitor::_sync_FutileWakeups != NULL) {ObjectMonitor::_sync_FutileWakeups->inc() ;}++ nWakeups ;//Knob_SpinAfterFutile 默认1,最后的自旋获挣扎if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;//Knob_ResetEvent 默认0if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {Self->_ParkEvent->reset() ;OrderAccess::fence() ;}if (_succ == Self) _succ = NULL ;tty->print_cr("lock cnt : %d " , idx++ );// Invariant: after clearing _succ a thread *must* retry _owner before parking.OrderAccess::fence() ;}assert (_owner == Self , "invariant") ;assert (object() != NULL , "invariant") ;UnlinkAfterAcquire (Self, &node) ;//这个函数写的比较难读,简单的说就是抢到锁后处理cxq、entrylist队列。//就是从cxq和entrylist中排除当前线程if (_succ == Self) _succ = NULL ;assert (_succ != Self, "invariant") ;if (_Responsible == Self) {_Responsible = NULL ;//将_Responsible置为NULL(重要)OrderAccess::fence(); // Dekker pivot-point}if (SyncFlags & 8) {OrderAccess::fence() ;}return ;
}
monitorenter小结:
轻量锁往重量锁膨胀,先通过inflate函数膨胀 object->release_set_mark(markOopDesc::encode(m)),即markword设置10。在通过enter函数判断是否重入,然后自旋。自旋失败,通过EnterI函数加入,并放入cxq队列头部,将java线程park。而当前的os线程无限循环与EnterI函数中最后一个for来TryLock。最后抢到锁则通过UnlinkAfterAcquire函数将当前线程从cxq或entrylist队列剔除。剔除后cxq或entrylist队指向当前node(ObjectMonitor)的next
三、锁消除 monitorexit
void TemplateTable::monitorexit() {transition(atos, vtos);/** pop %rax cmp (%rax),%rax**/__ null_check(rax);const Address monitor_block_top(rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize);const Address monitor_block_bot(rbp, frame::interpreter_frame_initial_sp_offset * wordSize);const int entry_size = frame::interpreter_frame_monitor_size() * wordSize;Label found;// find matching slot{Label entry, loop;/** mov -0x40(%rbp),%rsi **/__ movptr(c_rarg1, monitor_block_top); //这两行都是从栈中/** lea -0x40(%rbp),%rdx **/__ lea(c_rarg2, monitor_block_bot); //获得lockrecord// of monitor block ┍ /** jmp bind(entry) **/__ jmpb(entry); ┆ ┆__ bind(loop);┆/** cmp 0x8(%rsi),%rax **/__ cmpptr(rax, Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()));┆/** je bind(found) **/__ jcc(Assembler::equal, found);// 【lockrecord1】 -0x40(%rbp)┆// otherwise advance to next entry // 【lockrecord2】 -0x50(%rbp)┆/** add $0x10,%rsi **/__ addptr(c_rarg1, entry_size); // 【lockrecord3】 -0x60(%rbp)└/**跳转到这**/__ bind(entry); //假设一个java方法中有3个synchronize // check if bottom reached //就会有3个lockrecord就线上面一样排列在栈中/** cmp %rdx,%rsi **/__ cmpptr(c_rarg1, c_rarg2); //因此这里在cmpptr(c_rarg1, c_rarg2)时,// if not at bottom then check this entry //rsi指向lockrecord1,rdx指向lockrecord3/** jne bind(loop) **/__ jcc(Assembler::notEqual, loop); //这样的话就需要循环找到当前对象} //在哪个lockrecord中// error handling. Unlocking was not block-structured/** callq InterpreterRuntime::throw_illegal_monitor_state_exception **/__ call_VM(noreg, CAST_FROM_FN_PTR(address,InterpreterRuntime::throw_illegal_monitor_state_exception));__ should_not_reach_here();// call run-time routine// rsi: points to monitor entry__ bind(found);/** push %rax **/__ push_ptr(rax); // 对象oop压栈__ unlock_object(c_rarg1);/** pop %rax **/__ pop_ptr(rax); // 对象oop弹出到rax寄存器
}void InterpreterMacroAssembler::unlock_object(Register lock_reg) {assert(lock_reg == c_rarg1, "The argument is only for looks. It must be rarg1");if (UseHeavyMonitors) {call_VM(noreg,CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),lock_reg);} else {Label done;const Register swap_reg = rax; // Must use rax for cmpxchg instructionconst Register header_reg = c_rarg2; // Will contain the old oopMarkconst Register obj_reg = c_rarg3; // Will contain the oop/** mov %r13,-0x38(%rbp) **/save_bcp(); // 保存当前bytecode point到栈中// Convert from BasicObjectLock structure to object and BasicLock// structure Store the BasicLock address into %rax/** lea (%rsi),%rax **/ //栈中的lockrecord暂存到rax寄存器lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset_in_bytes()));/** mov 0x8(%rsi),%rcx **/ //lockrecord中的oop指针存入rcx寄存器movptr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()));/** movq $0x0,0x8(%rsi) **/ //lockrecord的oop指针置空movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()), (int32_t)NULL_WORD);if (UseBiasedLocking) {biased_locking_exit(obj_reg, header_reg, done);}/** mov (%rax),%rdx **/ //lockrecord中lock的markOop存入rdxmovptr(header_reg, Address(swap_reg,BasicLock::displaced_header_offset_in_bytes()));// Test for recursion //对markOop判空/** test %rdx,%rdx **/testptr(header_reg, header_reg);// zero for recursive case //空表示无锁直接跳出/** je bind(done) **/jcc(Assembler::zero, done);// Atomic swap back the old headerif (os::is_MP()) lock(); /** lock **/// rcx存着对象oop,将oop中的markword,通过rax和rdx对比,相同则设置到rdx中并设置zf位为1/** cmpxchg %rdx,(%rcx) **/cmpxchgptr(header_reg, Address(obj_reg, 0));// zero for recursive case/** je bind(done) **/jcc(Assembler::zero, done);/** mov %rcx,0x8(%rsi) **/movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()),obj_reg); // restore obj/** callq InterpreterRuntime::monitorexit **/ //执行InterpreterRuntime::monitorexitcall_VM(noreg,CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),lock_reg);bind(done);/** mov -0x38(%rbp),%r13 **/restore_bcp();}
}IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);
#endifHandle h_obj(thread, elem->obj());
.............................................................ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
.............................................................
IRT_ENDvoid ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {fast_exit (object, lock, THREAD) ;
}void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");// if displaced header is null, the previous enter is recursive enter, no-opmarkOop dhw = lock->displaced_header();//获取lockrecord中lock的markOopmarkOop mark ;if (dhw == NULL) { //能进来这个函数一般不会是null// Recursive stack-lock.// Diagnostics -- Could be: stack-locked, inflating, inflated.mark = object->mark() ;assert (!mark->is_neutral(), "invariant") ;if (mark->has_locker() && mark != markOopDesc::INFLATING()) {assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;}if (mark->has_monitor()) {ObjectMonitor * m = mark->monitor() ;assert(((oop)(m->object()))->mark() == mark, "invariant") ;assert(m->is_entered(THREAD), "invariant") ;}return ;}mark = object->mark() ;//获取对象oop的markword(不出意外应该是ObjectMonitor*指正按位或2)// lock中存不出意外应该是monitorenter时的栈地址if (mark == (markOop) lock) {assert (dhw->is_neutral(), "invariant") ;if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {TEVENT (fast_exit: release stacklock) ;return;}}ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {//...................................................................for (;;) {//此时应该是存在ObjectMonitor ,所以只要获得ObjectMonitor指针就可以返回了if (mark->has_monitor()) {ObjectMonitor * inf = mark->monitor() ;assert (inf->header()->is_neutral(), "invariant");assert (inf->object() == object, "invariant") ;assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");return inf ;}}//..........................................................................................
}void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {Thread * Self = THREAD ;if (THREAD != _owner) {//需要解锁的线程是否为当前线程if (THREAD->is_lock_owned((address) _owner)) {assert (_recursions == 0, "invariant") ;_owner = THREAD ;_recursions = 0 ;OwnerIsThread = 1 ;} else {// NOTE: we need to handle unbalanced monitor enter/exit// in native code by throwing an exception.// TODO: Throw an IllegalMonitorStateException ?TEVENT (Exit - Throw IMSX) ;assert(false, "Non-balanced monitor enter/exit!");if (false) {THROW(vmSymbols::java_lang_IllegalMonitorStateException());}return;}}if (_recursions != 0) {//如果是重入锁_recursions--; // this is simple recursive enterTEVENT (Inflated exit - recursive) ;return ;}// SyncFlags默认是0,_Responsible置空if ((SyncFlags & 4) == 0) {_Responsible = NULL ;}#if INCLUDE_TRACE// get the owner's thread id for the MonitorEnter event// if it is enabled and the thread isn't suspendedif (not_suspended && Tracing::is_event_enabled(TraceJavaMonitorEnterEvent)) {_previous_owner_tid = SharedRuntime::get_java_tid(Self);}
#endiffor (;;) {assert (THREAD == _owner, "invariant") ;
//Knob_ExitPolicy 默认0if (Knob_ExitPolicy == 0) {OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lockOrderAccess::storeload() ; // See if we need to wake a successor//没有线程需要获取锁 直接返回if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {TEVENT (Inflated exit - simple egress) ;return ;}TEVENT (Inflated exit - complex egress) ;//在下面代码中通过ExitEpilog函数来解锁的_owner都被置空了,这里直接returnif (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {return ;}TEVENT (Exit - Reacquired) ;} else {if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lockOrderAccess::storeload() ;// Ratify the previously observed values.if (_cxq == NULL || _succ != NULL) {TEVENT (Inflated exit - simple egress) ;return ;}if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {TEVENT (Inflated exit - reacquired succeeded) ;return ;}TEVENT (Inflated exit - reacquired failed) ;} else {TEVENT (Inflated exit - complex egress) ;}}guarantee (_owner == THREAD, "invariant") ;ObjectWaiter * w = NULL ;//Knob_QMode 默认为0 //QMode:抢锁者队列的唤醒顺序用哪个模式int QMode = Knob_QMode ;if (QMode == 2 && _cxq != NULL) { //从cxq队列唤醒w = _cxq ;//cxq队列头节点assert (w != NULL, "invariant") ;assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;ExitEpilog (Self, w) ;//将w这个节点中的owner置空已达到解锁的目的 并唤醒其他java线程来抢锁return ;}if (QMode == 3 && _cxq != NULL) {w = _cxq ;for (;;) {assert (w != NULL, "Invariant") ;ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;if (u == w) break ;w = u ;}assert (w != NULL , "invariant") ;ObjectWaiter * q = NULL ;ObjectWaiter * p ;for (p = w ; p != NULL ; p = p->_next) {//设置TState为TS_ENTER ,在MonitorEnter抢到锁时有使用guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;p->TState = ObjectWaiter::TS_ENTER ;p->_prev = q ;q = p ;}ObjectWaiter * Tail ;for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;if (Tail == NULL) {_EntryList = w ;} else {//QMode=3 如果entrylist不为空,则将cxq链接entrylist尾部 entrylist = entrylist->cxq,Tail->_next = w ;w->_prev = Tail ;}}if (QMode == 4 && _cxq != NULL) {w = _cxq ;for (;;) {assert (w != NULL, "Invariant") ;ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;if (u == w) break ;w = u ;}assert (w != NULL , "invariant") ;ObjectWaiter * q = NULL ;ObjectWaiter * p ;for (p = w ; p != NULL ; p = p->_next) {guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;p->TState = ObjectWaiter::TS_ENTER ;p->_prev = q ;q = p ;}//QMode=4 将cxq链接到entryList头部 entrylist = cxq->entrylistif (_EntryList != NULL) {q->_next = _EntryList ;_EntryList->_prev = q ;}_EntryList = w ;}//QMode默认情况下从这里开始w = _EntryList ;//看情况,如果多个线程竞争,这里就不为null//具体看UnlinkAfterAcquire函数的if (SelfNode->TState == ObjectWaiter::TS_ENTER)判断这段if (w != NULL) {assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;ExitEpilog (Self, w) ;//将w这个节点中的owner置空已达到解锁的目的 并唤醒其他java线程来抢锁return ;}//走这里表示不存在_EntryList队列的竞争者,//例如:在一个线程解锁时将要解锁线程在队列中node的Tstate设置为TS_ENTER,//在这个间隙时间正好有一个不存在队列中的线程来抢锁并抢到锁,//此时该线程加入cxq时的node的Tstate是TS_CXQ,这样就即便上锁成功也不会被放入_EntryList队列//那么走到这里表示_EntryList 中的竞争全解锁完了在轮到这w = _cxq ;if (w == NULL) continue ;for (;;) {assert (w != NULL, "Invariant") ;ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;//_cxq头结点置空if (u == w) break ;w = u ;}TEVENT (Inflated exit - drain cxq into EntryList) ;assert (w != NULL , "invariant") ;assert (_EntryList == NULL , "invariant") ;if (QMode == 1) {// QMode == 1 : drain cxq to EntryList, reversing order// We also reverse the order of the list.ObjectWaiter * s = NULL ;ObjectWaiter * t = w ;ObjectWaiter * u = NULL ;while (t != NULL) {//队列转置,QMode =1 :1234变成4321guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;t->TState = ObjectWaiter::TS_ENTER ;u = t->_next ;t->_prev = u ;t->_next = s ;s = t;t = u ;}_EntryList = s ;assert (s != NULL, "invariant") ;} else {// QMode == 0 or QMode == 2 队列顺序不变,让_EntryList 指向cxq,// 并每个node的TState 设置为TS_ENTER _EntryList = w ;ObjectWaiter * q = NULL ;ObjectWaiter * p ;for (p = w ; p != NULL ; p = p->_next) {guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;p->TState = ObjectWaiter::TS_ENTER ;p->_prev = q ;q = p ;}}if (_succ != NULL) continue;w = _EntryList ;if (w != NULL) {guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;ExitEpilog (Self, w) ;//将w这个节点中的owner置空已达到解锁的目的 并唤醒其他java线程来抢锁return ;}}
}
总结
上锁:
无论是偏向锁还是轻量、重量锁都需要从os堆栈的rbp - 0x40到(rbp - 0x40)-0x10位置开辟16字节的空间用作lock record。lock record即BasicObjectLock,该类中存在两个变量 BasicLock _lock; 和oop _obj; _lock变量用于存储锁相关的内容。
TemplateTable::monitorenter()中movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax)
将oop设置BasicObjectLock的oop变量中
偏向锁:
通过lock_object->biased_locking_enter函数中orptr(tmp_reg, r15_thread)
设置到BasicObjectLock的BasicLock _lock然后通过cmpxchgptr(tmp_reg, mark_addr)
设置到oop的markword中
轻量锁 | 重量锁:
寄存器对应关系:
swap_reg = rax,
oop = obj_reg = rcx,
lock record = lock_reg = rsi。
在lock_object函数中movl(swap_reg, 1)
生成一个临时锁头放入无锁标识,orptr(swap_reg, Address(obj_reg, 0))
临时锁头与当前oop中的markWord做异或,如果是无锁则001 | 1 = 1、已是轻量锁00 | 1 = 00、已是重量锁010 | = 11 设置到临时锁头中,轻量锁和重量锁下其他61位则是线程栈地址。下一步movptr(Address(lock_reg, mark_offset), swap_reg)
将临时锁头设置到lock record的 BasicLock _lock变量中,
此时 lock_reg 指向线程栈地址,lock_reg 中的_lock在有锁的情况存着线程栈地址 | 1,无锁则是1。obj_reg中的首地址有锁则是上一个上锁线程的线程栈lock record,无锁则是1。swap_reg则根据obj_reg情况来确定。
通过cmpxchgptr(lock_reg, Address(obj_reg, 0))
cmpxchg
轻量锁:
在无锁->轻量锁下,obj_reg的首地址指针指向的内存:markWord和swap_reg 中临时无锁标志位都是01无锁,cmpxchg对比先等,将lock_reg设置到markWord并设置ZF位为1。最后jcc(Assembler::zero, done),此时ZF位=1,则跳转到一下字节码。
重量锁:
cmpxchg 对比不相等表示oop中markword有存在线程地址或线程栈地址,markword也会通过cmpxchg 设置到swap_reg ,然后走slow_case,进入ObjectSynchronizer::slow_enter函数。
ObjectSynchronizer::slow_enter函数中会先判断oop的markword是否包含了ObjectMonitor* | 10的值,linux中jdk1.8一个对象oop如果上过重量锁,markword中的这个值是不会回收(windows会)。
通过调用ObjectSynchronizer::inflate(THREAD, obj())
函数封装一个ObjectMonitor * m,如果markword已存在ObjectMonitor* | 10的值,则用markword^10来获得ObjectMonitor*。如果不存在将锁标志位膨胀成10即重量锁标志放入ObjectMonitor 中, 在将这个ObjectMonitor指针与10做按位与运算,到oop的markword中返回,并设置一个 设置_owner当前线程指针,后返回。继而调用ObjectMonitor::enter(TRAPS)
函数,该函数中会通过
//判断当前ObjectMonitor是否被一个线程拥有
//如果是已经过上锁并解锁的对象_owner线程指针会在解锁过程中置空,cur =null
//如果是膨胀对象先会在inflate函数设置_owner线程指针cur = _owner
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;if (cur == NULL) {// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.assert (_recursions == 0 , "invariant") ;assert (_owner == Self, "invariant") ;// CONSIDER: set or assert OwnerIsThread == 1return ;}
然后进行现自旋以及抢锁,大概流程是:
自旋(TrySpin)->抢锁(TryLock)->自旋->进入cxq队列头->抢锁->死循环(抢锁->抢锁->自旋)获得锁后进入ObjectMonitor::UnlinkAfterAcquire (Thread * Self, ObjectWaiter * SelfNode)函数来处理队列
抢锁的线程会被ObjectWaiter node(Self)封装后才会进入cxq队列,ObjectMonitor::UnlinkAfterAcquire函数中会判断当前node的TState,TState初始时是ObjectWaiter::TS_CXQ。当一个线程解锁时会将队列中所有竞争者的TState设置为ObjectWaiter::TS_ENTER。在函数中如果是TS_CXQ将当前node从cxq中剔除,如果是TS_ENTER除了node剔除还会设置EntryList指向当前node的next节点。
解锁:
增加一个寄存器对应关系:header_reg = rdx
1、TemplateTable::monitorexit()函数中获得栈中lock record
2、在TemplateTable::unlock_object中将lock record中的锁头BasicLock _lock设置到swap_reg,oop _obj设置到obj_reg,swap_reg指向的内容即无锁标志设置到header_reg中
3、lock record中的oop指针置空(置0)
4、通过cmpxchg,对比swap_reg和obj_reg。
4.1、 相等:轻量锁 将header_reg无锁标志设置到对象oop的marword中跳出解锁
4.2、不相等(默认重量锁不考虑其他情况):准备进入InterpreterRuntime::monitorexit函数,调用顺序
InterpreterRuntime::monitorexit->ObjectSynchronizer::slow_exit->ObjectSynchronizer::fast_exit
ObjectSynchronizer::fast_exit函数中先调用ObjectSynchronizer::inflate,该函数通过oop的markword中的值和monitor_value取非来获得ObjectMonitor指针(ObjectMonitor*) (value() ^ monitor_value)
然后调用ObjectMonitor::exit。最后调用ExitEpilog删除节点中_owner(拥有锁的线程)
不考虑重入,QMode=0的情况下,先将_EntryList的头结点拿出不为空(不为空原因结合结合抢锁成功后调用ObjectMonitor::UnlinkAfterAcquire看)则调用ObjectMonitor::ExitEpilog函数将这个节点的_owner线程指针置空(这里和上面的抢锁呼应上了)。_EntryList为空的话就从cxq中取头结点,将_EntryList指向cxq并将所有节点的TStat设置为ObjectWaiter::TS_ENTER,在将_EntryList的头结点拿出调用ObjectMonitor::ExitEpilog_owner置空线程指针
synchronized源码解析相关推荐
- synchronized 和 reentrantlock 区别是什么_JUC源码系列之ReentrantLock源码解析
目录 ReentrantLock 简介 ReentrantLock 使用示例 ReentrantLock 与 synchronized 的区别 ReentrantLock 实现原理 Reentrant ...
- 彻底理解OkHttp - OkHttp 源码解析及OkHttp的设计思想
OkHttp 现在统治了Android的网络请求领域,最常用的框架是:Retrofit+okhttp.OkHttp的实现原理和设计思想是必须要了解的,读懂和理解流行的框架也是程序员进阶的必经之路,代码 ...
- Framework 源码解析知识梳理(5) startService 源码分析
一.前言 最近在看关于插件化的知识,遇到了如何实现Service插件化的问题,因此,先学习一下Service内部的实现原理,这里面会涉及到应用进程和ActivityManagerService的通信, ...
- HandlerThread和IntentService源码解析
简介 首先我们先来了解HandlerThread和IntentService是什么,以及为什么要将这两者放在一起分析. HandlerThread: HandlerThread 其实是Handler ...
- EventBus源码解析
前面一篇文章讲解了EventBus的使用,但是作为开发人员,不能只停留在仅仅会用的层面上,我们还需要弄清楚它的内部实现原理.所以本篇博文将分析EventBus的源码,看看究竟它是如何实现"发 ...
- spring aop 注入源码解析
spring aop 注入源码解析 aop启动 AbstractApplicationContext.java @Overridepublic void refresh() throws BeansE ...
- Volley 源码解析之图片请求
一.前言 上篇文章我们分析了网络请求,这篇文章分析对图片的处理操作,如果没看上一篇,可以先看上一篇文章Volley 源码解析之网络请求.Volley 不仅仅对请求网络数据作了良好的封装,还封装了对图片 ...
- 死磕 java同步系列之ReentrantReadWriteLock源码解析
问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...
- Handler消息机制(九):IntentService源码解析
作者:jtsky 链接:https://www.jianshu.com/p/0a150ec09a32 简介 首先我们先来了解HandlerThread和IntentService是什么,以及为什么要将 ...
最新文章
- RxJava firstElement 与 lastElement 以及 elementAt
- 利用Use Case为系统行为建模(3)
- 运维开发必会技能之一——虚拟机管理
- python set没有顺序_Python一题多解学思路:指定列前置
- 关于flex布局的深入学习
- 解决问题的能力 10倍程序员
- PyTorch是个啥玩意儿?
- openssh8.6升级修复(CVE-2020-15778)(CVE-2018-15919)(CVE-2017-15906)等漏洞
- 基于Teigha.Net实现CAD到SHP的转换方案
- 【Elasticsearch】Elasticsearch的数据类型 (text、keyword、date、object、geo等)
- 企业微信发送应用消息的实现
- 浪涌保护器ant120_ANT120/530/1P浪涌保护器服务周到漳州
- 人工智能产品经理如何面对数据挖掘
- Python 计算两点之间的距离
- python一阶差分_Python使用pandas对数据进行差分运算的方法
- R语言——矩阵中删除缺省值可用的函数
- 骨传导耳机是利用什么原理听歌?什么骨传导耳机好用
- 停止抱怨英语_停止抱怨
- matlab实验报告井字棋,有偿井字棋游戏300+
- 优化策略5 Label Smoothing Regularization_LSR原理分析