我的环境 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
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.class
Last modified 2022-12-17; size 394 bytes
MD5 checksum ed0edf2dd2f862dbd54dd1820b813ed4
Compiled from "Test.java"
public class Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#17 // java/lang/Object."":()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 // "":()V
#18 = NameAndType #9:#10 // exp:(I)I
#19 = Utf8 Test
#20 = Utf8 java/lang/Object
#21 = Utf8 java/lang/InterruptedException
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 2: 0
public static synchronized int exp(int) throws java.lang.InterruptedException;
descriptor: (I)I
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=1, locals=1, args_size=1
0: iconst_0
1: ireturn
LineNumberTable:
line 5: 0
Exceptions:
throws java.lang.InterruptedException
public static void main(java.lang.String[]) throws java.lang.InterruptedException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: iconst_1
1: invokestatic #2 // Method exp:(I)I
4: pop
5: return
LineNumberTable:
line 9: 0
line 10: 5
Exceptions:
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 method
const 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 aligned
oop _obj; // object holds the lock;
}
lock_method函数结束。总结一下,这里只变更了两个地方:rax寄存器,存着镜像对象oop;栈空间提升了0x10字节,存着lock record
接着分析lock_object(rdx)函数
//字节码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修饰,最终上锁的逻辑都是通过这个函数来操作的
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 instruction
const Register obj_reg = rcx; // Will contain the oop
const 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),%rcx
if (UseBiasedLocking) {
biased_locking_enter(lock_reg, obj_reg, swap_reg, noreg, false, done, &slow_case);
}
............................................................................
bind(done);
}
}
偏向锁有两种状态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)
最简单的说就是看markword布局百度一下一大把。101即偏向,准确的说thread指针按位或5(即二进制101)才是偏向锁,jvm的对象在初始时是01即无锁状态,启动时通过一个线程延迟调度将对象设置成101此时并没有java线程对对象进行加锁,何来偏向锁。最多只能说该对象是无锁,但是因为没上或者没上过锁,所以该对象是一个可以偏向某一线程的无锁对象。
//创建偏向的堆栈
BiasedLocking::init biasedLocking.cpp:97
Threads::create_vm thread.cpp:3663
JNI_CreateJavaVM jni.cpp:5207
InitializeJVM java.c:1214
JavaMain java.c:376
//设置偏向的线程堆栈
enable_biased_locking biasedLocking.cpp:42
Dictionary::classes_do dictionary.cpp:281
SystemDictionary::classes_do systemDictionary.cpp:1764
VM_EnableBiasedLocking::doit biasedLocking.cpp:57
VM_Operation::evaluate vm_operations.cpp:62
VMThread::evaluate_operation vmThread.cpp:377
VMThread::loop vmThread.cpp:502
VMThread::run vmThread.cpp:276
java_start os_linux.cpp:828
看下源码
//biasedLocking.cpp
static void enable_biased_locking(Klass* k) {
k->set_prototype_header(markOopDesc::biased_locking_prototype());
}
设置的内容:
可以看出只是设置了一个101
其他位并没有被使用。
来看看真正的偏向锁。为了方便将偏向延迟设置为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, // rsi
Register obj_reg, // rcx
Register swap_reg, // rax
Register tmp_reg, // r10
bool 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 判断偏向锁位是否为1
if (need_tmp_reg) {
pop(tmp_reg);
}
jcc(Assembler::notEqual, cas_label); // 不可偏向直接跳转退出当前函数对应的执行流
#ifndef _LP64
movptr(saved_mark_addr, swap_reg);
#endif
if (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 _LP64
orptr(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 _LP64
movptr(tmp_reg, swap_reg); //将其他位和
orptr(tmp_reg, r15_thread);//线程指针整合成一个markoop
#else
//..................................32位逻辑不管.................................
#endif
if (os::is_MP()) {
lock(); //汇编层面的lock
}
cmpxchgptr(tmp_reg, mark_addr); //cmpxchg %r10,(%rcx) rax和r10对比,相同设置到rcx中指针指向的内存,即oop中的markword
if (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 _LP64
orptr(tmp_reg, r15_thread);
#else
//..................................32位逻辑不管.................................
#endif
if (os::is_MP()) {
lock();
}
cmpxchgptr(tmp_reg, mark_addr); // compare tmp_reg and swap_reg
if (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_reg
if (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;
}
//这两段中的逻辑和初次上偏向锁的基本一样不多述了
为了方便先将偏向锁关闭
//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)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=1
0: getstatic #2 // Field syncTest:LTest;
3: dup
4: astore_1
5: monitorenter
6: iconst_1
7: istore_2
8: getstatic #3 // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
11: ldc2_w #4 // long 1000000000l
14: invokevirtual #6 // Method java/util/concurrent/TimeUnit.sleep:(J)V
17: aload_1
18: monitorexit
19: goto 27
22: astore_3
23: aload_1
24: monitorexit
25: aload_3
26: athrow
27: iconst_0
28: ireturn
Exception table:
from to target type
6 19 22 any
22 25 22 any
LineNumberTable:
line 9: 0
line 10: 6
line 11: 8
line 12: 17
line 14: 27
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 22
locals = [ int, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
Exceptions:
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 instruction
const Register obj_reg = rcx; // Will contain the oop
const 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),%rcx
if (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); //先无锁头载入lockrecord
assert(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 bottom
sub $0x10,%rsp __ subptr(rsp, entry_size); //rsp-0x10是开辟新的lockrecord
sub $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_case
subptr(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);
}
}
这段主要使用了三个寄存器,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函数进行膨胀
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 inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
..........................................................................................
IRT_END
void 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到markword
TEVENT (slow_enter: release stacklock) ;
return ;
}
// Fall through to inflate() ...
} else
if (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/class
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ; //设置0,锁的膨胀中状态
if (cmp != mark) {//或许是防止多线程cmpxchg跟换markword
omRelease (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 monitor
m->set_header(dmw) ; //设置无锁头
m->set_owner(mark->locker()); //设置当前线程栈(也就是属于哪个线程)
m->set_object(object);//设置当前对象的oop
guarantee (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 - TATAS
if (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 self
if (_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 默认0
if ((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 ;
}
轻量锁往重量锁膨胀,先通过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
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 instruction
const Register header_reg = c_rarg2; // Will contain the old oopMark
const 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存入rdx
movptr(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 header
if (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::monitorexit
call_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 ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
Handle h_obj(thread, elem->obj());
.............................................................
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
.............................................................
IRT_END
void 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-op
markOop dhw = lock->displaced_header();//获取lockrecord中lock的markOop
markOop 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 enter
TEVENT (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 suspended
if (not_suspended && Tracing::is_event_enabled(TraceJavaMonitorEnterEvent)) {
_previous_owner_tid = SharedRuntime::get_java_tid(Self);
}
#endif
for (;;) {
assert (THREAD == _owner, "invariant") ;
//Knob_ExitPolicy 默认0
if (Knob_ExitPolicy == 0) {
OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
OrderAccess::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都被置空了,这里直接return
if (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 lock
OrderAccess::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->entrylist
if (_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变成4321
guarantee (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 == 1
return ;
}
然后进行现自旋以及抢锁,大概流程是:
自旋(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置空线程指针