目錄
3、InterpreterRuntime::monitorenter
5、InterpreterRuntime::monitorexit
6、monitorenter/ monitorexit 指令的編譯執行
本篇博客來從字節碼實現層面詳細探討synchronized與volatile關鍵字的實現細節。
一、synchronized用法
1、修飾實例方法
修飾實例方法時,執行該方法時必須獲取某個實例關聯的鎖,即併發調用同一實例的多個被修飾的實例方法時只能執行其中的某一個,測試用例如下:
public class AddTest {
private int a;
synchronized int add(){
a++;
return a;
}
synchronized int add2(){
a+=2;
return a;
}
}
private final long NUM=100000000;
@Test
public void test() throws Exception {
AddTest a=new AddTest();
Thread t=new Thread(new Runnable() {
@Override
public void run() {
long startTime= System.currentTimeMillis();
for(int i=0;i<NUM;i++) {
a.add();
}
long time=System.currentTimeMillis()-startTime;
System.out.println("t run end,time->"+time);
}
});
t.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
long startTime= System.currentTimeMillis();
for(int i=0;i<NUM;i++) {
a.add2();
}
long time=System.currentTimeMillis()-startTime;
System.out.println("t2 run end,time->"+time);
}
});
t2.start();
t.join();
t2.join();
System.out.println("job end");
}
@Test
public void test2() throws Exception {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i<NUM;i++) {
a.add();
}
long time=System.currentTimeMillis()-startTime;
System.out.println("t run end,time->"+time);
}
});
t.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i<NUM;i++) {
a.add2();
}
long time=System.currentTimeMillis()-startTime;
System.out.println("t2 run end,time->"+time);
}
});
t2.start();
t.join();
t2.join();
System.out.println("job end");
}
第一個測試用例就是併發調用同一個實例的兩個被修飾的實例方法,其執行結果如下:
第二個測試用例是併發調用不同實例的兩個被修飾的實例方法,其執行結果如下:
第一個測試用例因爲兩個方法在同一時間只能執行其中一個,而第二個測試用例是兩個方法在並行執行互不影響,因此第一個的耗時是第二個的兩倍多,超過兩倍的耗時是獲取鎖釋放鎖等同步操作的損耗。
2、修飾靜態方法
修飾靜態方法時,執行該方法時必須獲取該類的class如String.class實例關聯的鎖,即同一時間多個不同實例併發調用不同的靜態方法時只能執行其中的一個方法,測試用例如下:
public class AddTest {
private int a;
private static int b;
synchronized int add(){
a++;
return a;
}
synchronized int add2(){
a+=2;
return a;
}
synchronized static int add3(){
b++;
return b;
}
synchronized static int add4(){
b+=2;
return b;
}
}
@Test
public void test2() throws Exception {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i<NUM;i++) {
a.add();
}
long time=System.currentTimeMillis()-startTime;
System.out.println("t run end,time->"+time);
}
});
t.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i<NUM;i++) {
a.add2();
}
long time=System.currentTimeMillis()-startTime;
System.out.println("t2 run end,time->"+time);
}
});
t2.start();
t.join();
t2.join();
System.out.println("job end");
}
@Test
public void test3() throws Exception {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i<NUM;i++) {
a.add3();
}
long time=System.currentTimeMillis()-startTime;
System.out.println("t run end,time->"+time);
}
});
t.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i<NUM;i++) {
a.add4();
}
long time=System.currentTimeMillis()-startTime;
System.out.println("t2 run end,time->"+time);
}
});
t2.start();
t.join();
t2.join();
System.out.println("job end");
}
test2的耗時不變,如下圖:
test3的耗時跟test1差不過了,如下圖:
這是因爲他們都是同一時間只能執行其中的某一個方法,不同的是test1是獲取某個實例關聯的鎖,test3是獲取類的class實例關聯的鎖。
3、修飾代碼塊
修飾代碼塊時需要指定在哪個實例上同步,如果該實例是某個類的class實例或者靜態屬性時,則同樣的,該類的多個不同實例併發調用該代碼塊時同一時間只能其中一個能執行;如果該實例是某個實例屬性或者this時,則同樣的,併發調用同一實例的該代碼塊時只能執行其中一個;如果該實例是局部變量,因爲局部變量是執行該方法時私有的,其他線程不會訪問到該變量,也就不會去獲取該實例關聯的鎖了,所以實際相當於沒有加鎖,JVM優化時會將該鎖自動消除。
測試用例如下:
public class AddTest {
private int a;
private static int b;
int add(){
synchronized (this) {
a++;
}
return a;
}
int add2(){
synchronized (this) {
a+=2;
}
return a;
}
static int add3(){
synchronized (AddTest.class) {
b++;
}
return b;
}
static int add4(){
synchronized (AddTest.class) {
b+=2;
}
return b;
}
}
public class AddTest {
private int a;
private static int b;
private Object lock=new Object();
private static Object staticLock=new Object();
int add(){
synchronized (lock) {
a++;
}
return a;
}
int add2(){
synchronized (lock) {
a+=2;
}
return a;
}
static int add3(){
synchronized (staticLock) {
b++;
}
return b;
}
static int add4(){
synchronized (staticLock) {
b+=2;
}
return b;
}
}
上述兩種情形下,執行test2和test3的結果和修飾方法時的結果是一樣的。
二、synchronized底層實現
synchronized的底層實現分爲兩種情形,一種是修飾方法,在JVM解釋或者編譯執行時發現如果某個方法聲明中包含有synchronized關鍵字就會執行對應的同步邏輯,注意實例方法與靜態方法在具體執行時其區別只有一個,前者會默認將當前實例作爲方法入參傳進去,後者則不做任何處理;另一種是修飾代碼塊,這個是通過特定字節碼來實現的,下面來分別說明。
1、修改代碼塊的字節碼分析
測試用例如下:
public class AddTest {
private int a;
private static int b;
int add(){
synchronized (this) {
a++;
}
return a;
}
static int add2(){
synchronized (AddTest.class) {
b++;
}
return b;
}
}
將上述代碼編譯後,通過javap -v獲取其具體的字節碼信息,結果如下:
int add();
descriptor: ()I
flags:
Code:
stack=3, locals=3, args_size=1
0: aload_0 //從局部變量表中加載第0個引用類型元素放到操作數棧棧頂,第0個元素是當前實例的引用,方法開始執行前由JVM放入進去的
1: dup //複製操作數棧頂的值並將其放到操作數棧頂
2: astore_1 //將操作數棧棧頂的值出棧並保存到局部變量表中索引爲1的位置
3: monitorenter //獲取鎖
4: aload_0
5: dup
6: getfield #2 //讀取屬性a,放到操作數棧棧頂
9: iconst_1 //將常量1放入到操作數棧棧頂
10: iadd //將棧頂的兩個操作數出棧,執行加法運算,將結果保存在棧頂中
11: putfield #2 //將結果寫入到屬性a中
14: aload_1 //從局部變量表中加載第1個引用類型元素放到操作數棧棧頂
15: monitorexit //釋放鎖
16: goto 24 //跳轉到24即aload_0指令處開始執行
19: astore_2 //如果從4到16的字節碼執行過程中出現異常,異常處理器會跳轉到第19處的字節碼指令,此時棧頂保存着具體的異常實例
20: aload_1
21: monitorexit //釋放鎖
22: aload_2
23: athrow //拋出異常,終止方法執行
24: aload_0
25: getfield #2 //讀取屬性a放入棧頂
28: ireturn
Exception table: //從code屬性的exception_table屬性中解析出來的,from和to是try/catch的起始範圍,target是捕獲異常後的處理指令,type是捕獲異常的異常類型
from to target type
4 16 19 any
19 22 19 any
LineNumberTable: //該方法的LineNumberTable屬性
line 9: 0
line 10: 4
line 11: 14
line 12: 24
LocalVariableTable://局部變量表,對應於方法的LocalVariableTable屬性
Start Length Slot Name Signature
0 29 0 this LsynchronizedTest/AddTest;
StackMapTable: number_of_entries = 2 //對應於方法的StackMapTable屬性,用於JVM的類型檢查
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class synchronizedTest/AddTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
static int add2();
descriptor: ()I
flags: ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: ldc #3 從運行時常量池中提取AddTest.class放到操作數棧棧頂
2: dup
3: astore_0
4: monitorenter //獲取鎖
5: getstatic #4 //讀取靜態屬性b
8: iconst_1
9: iadd
10: putstatic #4 //將結果寫入靜態屬性b
13: aload_0
14: monitorexit //釋放鎖
15: goto 23 //跳轉到23處的指令執行
18: astore_1 //如果出現異常跳轉到此處執行,操作數棧頂保存了異常Exception實例
19: aload_0
20: monitorexit //釋放鎖
21: aload_1
22: athrow //拋出異常
23: getstatic #4 // Field b:I
26: ireturn
Exception table:
from to target type
5 15 18 any
18 21 18 any
LineNumberTable:
line 16: 0
line 17: 5
line 18: 13
line 19: 23
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 18
locals = [ class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
由上述分析可知,synchronized修飾代碼塊時會被翻譯成monitorenter和monitorexit指令對,monitorenter用於獲取鎖,monitorexit用於釋放鎖,並且還會自動加上異常處理邏輯,會捕獲所有的異常,捕獲完成後就通過monitorexit釋放鎖並拋出異常。
2、monitorenter 指令實現
如何查看各字節碼的底層實現可以參考《Hotspot 方法調用之TemplateInterpreter 源碼解析》,monitorenter 指令的實現如下:
void TemplateTable::monitorenter() {
//校驗當前指令的棧頂緩存類型是否正確
transition(atos, vtos);
//校驗rax中值是否爲空,棧頂緩存就保存在rax寄存器中,如果爲NULL會觸發底層操作系統的NULL異常
//此時rax中保存的是用於獲取鎖的實例oop
__ 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;
//xorl用於按位異或,相同的位置爲0,不同的位置爲1,此處是將c_rarg1置爲NULL
__ xorl(c_rarg1, c_rarg1); // points to free slot or NULL
//找到一個空閒的monitor_block,結果保存在c_rarg1中
{
Label entry, loop, exit;
//將monitor_block_top拷貝到c_rarg3中
__ movptr(c_rarg3, monitor_block_top); // points to current entry,
// starting with top-most entry
//將monitor_block_bot拷貝到c_rarg2
__ lea(c_rarg2, monitor_block_bot); // points to word before bottom
// of monitor block
//跳轉到entry標籤處執行
__ jmpb(entry);
__ bind(loop);
//判斷c_rarg3指向的BasicObjectLock的obj屬性是否爲空,如果爲空表示未使用
__ cmpptr(Address(c_rarg3, BasicObjectLock::obj_offset_in_bytes()), (int32_t) NULL_WORD);
//如果相等,即BasicObjectLock的obj屬性爲空,則將c_rarg3的值拷貝到c_rarg1
__ cmov(Assembler::equal, c_rarg1, c_rarg3);
// 判斷c_rarg3指向的BasicObjectLock的obj屬性與rax中實例是否一致
__ cmpptr(rax, Address(c_rarg3, BasicObjectLock::obj_offset_in_bytes()));
// 如果一致則退出,一致說明BasicObjectLock的obj屬性不爲空
__ jccb(Assembler::equal, exit);
// 如果不一致則把c_rarg3地址加上entry_size,即開始遍歷下一個monitor_block
__ addptr(c_rarg3, entry_size);
__ bind(entry);
//判斷兩個寄存器的值是否相等
__ cmpptr(c_rarg3, c_rarg2);
//如果不等於則跳轉到loop標籤,否則跳轉到exit
__ jcc(Assembler::notEqual, loop);
__ bind(exit);
}
//判斷c_rarg1是否爲空,如果不爲空則跳轉到allocated處
__ testptr(c_rarg1, c_rarg1); // check if a slot has been found
__ jcc(Assembler::notZero, allocated); // if found, continue with that one
//如果沒有找到空閒的monitor_block則分配一個
{
Label entry, loop;
// 將monitor_block_bot拷貝到c_rarg1 // rsp: old expression stack top
__ movptr(c_rarg1, monitor_block_bot); // c_rarg1: old expression stack bottom
//向下(低地址端)移動rsp指針entry_size字節
__ subptr(rsp, entry_size); // move expression stack top
//將c_rarg1減去entry_size
__ subptr(c_rarg1, entry_size); // move expression stack bottom
//將rsp拷貝到c_rarg3
__ mov(c_rarg3, rsp); // set start value for copy loop
//將c_rarg1中的值寫入到monitor_block_bot
__ movptr(monitor_block_bot, c_rarg1); // set new monitor block bottom
//跳轉到entry處開始循環
__ jmp(entry);
// 2.移動monitor_block_bot到棧頂的數據,將從棧頂分配的一個monitor_block插入到原來的monitor_block_bot下面
__ bind(loop);
//將c_rarg3之後的entry_size處的地址拷貝到c_rarg2,即原來的rsp地址
__ movptr(c_rarg2, Address(c_rarg3, entry_size)); // load expression stack
// word from old location
//將c_rarg2中的數據拷貝到c_rarg3處,即新的rsp地址
__ movptr(Address(c_rarg3, 0), c_rarg2); // and store it at new location
//c_rarg3加上一個字寬,即準備複製下一個字寬的數據
__ addptr(c_rarg3, wordSize); // advance to next word
__ bind(entry);
//比較兩個寄存器的值
__ cmpptr(c_rarg3, c_rarg1); // check if bottom reached
//如果不等於則跳轉到loop
__ jcc(Assembler::notEqual, loop); // if not at bottom then
// copy next word
}
// call run-time routine
// c_rarg1: points to monitor entry
__ bind(allocated);
//增加r13,使其指向下一個字節碼指令
__ increment(r13);
//將rax中保存的獲取鎖的oop保存到c_rarg1指向的BasicObjectLock的obj屬性中
__ movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax);
//獲取鎖
__ lock_object(c_rarg1);
//保存bcp,爲了出現異常時能夠返回到原來的執行位置
__ save_bcp(); // in case of exception
__ generate_stack_overflow_check(0);
//因爲上面已經增加r13了,所以此處dispatch_next的第二個參數使用默認值0,即執行r13指向的字節碼指令即可,不用跳轉到下一個指令
__ dispatch_next(vtos);
}
void InterpreterMacroAssembler::lock_object(Register lock_reg) {
assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1");
//UseHeavyMonitors表示是否只使用重量級鎖,默認爲false,如果爲true則調用InterpreterRuntime::monitorenter方法獲取重量級鎖
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 = c_rarg3; // 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;
//將用於獲取鎖的實例oop拷貝到obj_reg中
movptr(obj_reg, Address(lock_reg, obj_offset));
//UseBiasedLocking默認爲true
if (UseBiasedLocking) {
//首先嚐試獲取偏向鎖,獲取成功會跳轉到done,否則走到slow_case
biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, false, done, &slow_case);
}
//如果UseBiasedLocking爲false會走此邏輯,即不使用偏向鎖,直接使用輕量級鎖
//將1拷貝到swap_reg
movl(swap_reg, 1);
//計算 object->mark() | 1,結果保存到swap_reg
orptr(swap_reg, Address(obj_reg, 0));
//將(object->mark() | 1)的結果保存到BasicLock的displaced_header中
movptr(Address(lock_reg, mark_offset), swap_reg);
assert(lock_offset == 0,
"displached header must be first word in BasicObjectLock");
if (os::is_MP()) lock(); //如果是多核系統則上鎖
//比較lock_reg和rax即swap_reg是否相等,如果相等則將obj原來的對象頭保存到lock_reg中,如果不能則將lock_reg拷貝到swap_reg中
cmpxchgptr(lock_reg, Address(obj_reg, 0));
if (PrintBiasedLockingStatistics) {
cond_inc32(Assembler::zero,
ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
}
//如果等於說明本身就持有輕量級鎖則跳轉到done
jcc(Assembler::zero, done);
// Test if the oopMark is an obvious stack pointer, i.e.,
// 1) (mark & 7) == 0, and
// 2) rsp <= mark < mark + os::pagesize()
//如果不等於,swap_reg減去rsp
subptr(swap_reg, rsp);
andptr(swap_reg, 7 - os::vm_page_size());
// Save the test result, for recursive case, the result is zero
movptr(Address(lock_reg, mark_offset), swap_reg);
if (PrintBiasedLockingStatistics) {
cond_inc32(Assembler::zero,
ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
}
//如果andptr的結果爲0,即已經獲取了輕量級鎖則跳轉到done
jcc(Assembler::zero, done);
//否則執行InterpreterRuntime::monitorenter獲取重量即鎖
bind(slow_case);
// Call the runtime routine for slow case
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
lock_reg);
bind(done);
}
}
其中BasicObjectLock是一個簡單的數據結構,其定義如下:
BasicLock主要用於保存對象的對象頭,其定義如下:
BasicObjectLock是內嵌在線程的棧幀中的,有一段連續的內存區域用來保存多個BasicObjectLock,這個內存區域的起始位置是保存在棧幀中的特定偏移處的,起始地址保存interpreter_frame_monitor_block_top_offset偏移處,終止地址保存在interpreter_frame_initial_sp_offset偏移處。monitorenter方法會首先從當前棧幀中已經分配的BasicObjectLock中找到一個空閒的或者obj屬性就是當前對象的BasicObjectLock(即之前已經分配過了),如果不存在空閒的則重新分配一個,獲取BasicObjectLock後就將當前對象保存進obj屬性,然後調用lock_object獲取鎖。在默認開啓UseBiasedLocking時,lock_object會先獲取偏向鎖,如果已經獲取了則升級成輕量級鎖,如果已經獲取了輕量級鎖則升級成重量級鎖。
3、InterpreterRuntime::monitorenter
InterpreterRuntime::monitorenter用於獲取輕量級鎖或者重量級鎖,其實現如下:
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
//獲取關聯的對象
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (UseBiasedLocking) {
//如果使用偏向鎖,走快速enter
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
"must be NULL or an object");
IRT_END
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
//如果不是在安全點上,嘗試獲取偏向鎖
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
//獲取成功直接返回
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
//如果在安全點下
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
slow_enter (obj, lock, THREAD) ;
}
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
if (mark->is_neutral()) {
//未持有鎖,保存原來的對象頭
lock->set_displaced_header(mark);
//將lock作爲原來的對象頭
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
//如果設置成功表示已經獲取輕量級鎖,返回
return ;
}
// Fall through to inflate() ...
} else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
//如果該對象已經持有輕量級鎖且對象頭中包含的指針屬於當前線程所有,即是當前線程持有了該對象的輕量級鎖
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
//已經分配了對應的BasicLock,這個BasicLock沒有用了
lock->set_displaced_header(NULL);
return;
}
//因爲ObjectMonitor中會保存對象的對象頭,所以此處不需要在保存了
lock->set_displaced_header(markOopDesc::unused_mark());
//鎖膨脹轉換成重量級鎖
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
bool Thread::is_lock_owned(address adr) const {
return on_local_stack(adr);
}
BasicLock* locker() const {
assert(has_locker(), "check");
return (BasicLock*) value();
}
4、monitorexit 指令實現
monitorexit指令用於釋放鎖,其實現如下:
void TemplateTable::monitorexit() {
//檢查棧頂緩存的類型是否正確
transition(atos, vtos);
//檢查rax包含的跟鎖關聯的對象oop是否爲空
__ 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;
//把monitor_block_top拷貝到c_rarg1
__ movptr(c_rarg1, monitor_block_top); // points to current entry,
// starting with top-most entry
//把monitor_block_bot拷貝到c_rarg2
__ lea(c_rarg2, monitor_block_bot); // points to word before bottom
// of monitor block
__ jmpb(entry);
__ bind(loop);
//比較rax中對象oop與obj屬性是否一致
__ cmpptr(rax, Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()));
//如果一致則表示找到了跳轉到found
__ jcc(Assembler::equal, found);
//如果沒有找到則增加entry_size,即開始遍歷下一個BasicObjectLock
__ addptr(c_rarg1, entry_size);
__ bind(entry);
//比較這兩個是否相等,如果相等表示遍歷完成
__ cmpptr(c_rarg1, c_rarg2);
//如果不等則跳轉到loop標籤
__ jcc(Assembler::notEqual, loop);
}
//沒有在當前線程的棧幀中找到關聯的BasicObjectLock,拋出異常
__ 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_ptr(rax); // make sure object is on stack (contract with oopMaps)
//執行解鎖邏輯
__ unlock_object(c_rarg1);
//從棧幀中彈出鎖對象
__ pop_ptr(rax); // discard object
}
void InterpreterMacroAssembler::unlock_object(Register lock_reg) {
assert(lock_reg == c_rarg1, "The argument is only for looks. It must be rarg1");
if (UseHeavyMonitors) {
//如果只使用重量級鎖,UseHeavyMonitors默認爲false
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
save_bcp(); //保存bcp,方便解鎖異常時回滾
//將lock屬性複製到swap_reg
lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset_in_bytes()));
//將obj屬性複製到obj_reg
movptr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()));
//將obj屬性置爲NULL
movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()), (int32_t)NULL_WORD);
if (UseBiasedLocking) {
//如果持有偏向鎖,則解鎖完成後跳轉到done
biased_locking_exit(obj_reg, header_reg, done);
}
//將BasicLock的displaced_header屬性複製到header_reg中,即該對象原來的對象頭
movptr(header_reg, Address(swap_reg,
BasicLock::displaced_header_offset_in_bytes()));
//判斷這個是否爲空
testptr(header_reg, header_reg);
//如果爲空說明已經完成了解鎖,則跳轉到done
jcc(Assembler::zero, done);
// Atomic swap back the old header
if (os::is_MP()) lock();//如果是多核系統則加鎖
//將header_reg與rax即swap_reg比較,如果相等則將obj_reg複製到header_reg,否則將header_reg複製到swap_reg
cmpxchgptr(header_reg, Address(obj_reg, 0));
//如果相等,說明當前線程本身持有該對象的輕量級鎖
jcc(Assembler::zero, done);
//如果不等,恢復obj屬性
movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()),
obj_reg); // restore obj
//調用InterpreterRuntime::monitorexit
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),
lock_reg);
bind(done);
//恢復bcp
restore_bcp();
}
}
//偏向鎖的解鎖只是判斷目標對象是否持有偏向鎖,如果持有就跳轉到done,沒有實際的解鎖動作
void MacroAssembler::biased_locking_exit(Register obj_reg, Register temp_reg, Label& done) {
assert(UseBiasedLocking, "why call this otherwise?");
//將obj的對象頭拷貝到temp_reg
movptr(temp_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
//將對象頭指針同biased_lock_mask_in_place求且
andptr(temp_reg, markOopDesc::biased_lock_mask_in_place);
//判斷且運算後的結果是否是5
cmpptr(temp_reg, markOopDesc::biased_lock_pattern);
//如果相等則跳轉到done
jcc(Assembler::equal, done);
}
5、InterpreterRuntime::monitorexit
monitorexit用於輕量級鎖和重量級鎖的釋放,其實現如下:
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
//如果BasicObjectLock爲空或者目標對象沒有持有鎖則拋出異常
if (elem == NULL || h_obj()->is_unlocked()) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
//將obj置爲NULL
elem->set_obj(NULL);
IRT_END
inline bool oopDesc::is_unlocked() const {
return mark()->is_unlocked();
}
bool is_unlocked() const {
return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value);
}
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();
markOop mark ;
if (dhw == NULL) {
//鎖嵌套的情形
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() ;
if (mark == (markOop) lock) {
//持有輕量級鎖,dhw保存着原來的對象頭
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) ;
}
6、monitorenter/ monitorexit 指令的編譯執行
上述幾個小節描述的都是在解釋器下的執行邏輯,那麼編譯器編譯後的執行邏輯了?參考ObjectSynchronizer::fast_enter和fast_exit的調用鏈,如下:
其中Runtime1就是C1編譯器,即client編譯器的運行時支持,OptoRuntime就是C2編譯器即server編譯器的運行時支持,SharkRuntime是OpenJDK特有的Shark編譯器的運行時支持,這三個的實現邏輯基本一致,以 Runtime1爲例說明:
JRT_ENTRY_NO_ASYNC(void, Runtime1::monitorenter(JavaThread* thread, oopDesc* obj, BasicObjectLock* lock))
Handle h_obj(thread, obj);
assert(h_obj()->is_oop(), "must be NULL or an object");
if (UseBiasedLocking) {
//UseBiasedLocking默認爲true,
ObjectSynchronizer::fast_enter(h_obj, lock->lock(), true, CHECK);
} else {
if (UseFastLocking) {
//UseFastLocking默認爲true
assert(obj == lock->obj(), "must match");
ObjectSynchronizer::slow_enter(h_obj, lock->lock(), THREAD);
} else {
lock->set_obj(obj);
ObjectSynchronizer::fast_enter(h_obj, lock->lock(), false, THREAD);
}
}
JRT_END
JRT_LEAF(void, Runtime1::monitorexit(JavaThread* thread, BasicObjectLock* lock))
assert(thread == JavaThread::current(), "threads must correspond");
assert(thread->last_Java_sp(), "last_Java_sp must be set");
// monitorexit is non-blocking (leaf routine) => no exceptions can be thrown
EXCEPTION_MARK;
oop obj = lock->obj();
assert(obj->is_oop(), "must be NULL or an object");
if (UseFastLocking) {
//UseFastLocking默認爲true
ObjectSynchronizer::slow_exit(obj, lock->lock(), THREAD);
} else {
ObjectSynchronizer::fast_exit(obj, lock->lock(), THREAD);
}
JRT_END
其直接調用在Runtime1::generate_code_for方法中,用於生成特定方法的執行Stub,具體如下:
其中save_live_registers用於保存寄存器中的數據,restore_live_registers用於將原來保存的數據恢復到寄存器中。可以進一步搜索monitorenter_id的調用鏈,如下:
MonitorEnterStub::emit_code和MonitorExitStub::emit_code在一起的,其實現如下:
_obj_reg和_lock_reg都是父類的屬性,通過構造函數傳遞進來的,查看MonitorEnterStub的構造函數的調用鏈,如下:
其中LIRGenerator::monitor_enter是處理synchronized修飾代碼塊情形的,LIRGenerator::do_Base中的調用是處理synchronized修飾方法情形的,前者的實現如下:
load_stack_address_monitor用於查找對應的或者分配一個新的BasicObjectLock,lock_object就是調用MonitorEnterStub的emit_code方法生成的彙編指令的,綜上分析可知編譯執行monitorenter/ monitorexit時,其邏輯和解釋執行時是基本一致的,核心的都在ObjectSynchronizer中,不過解釋執行時將其中的一部分邏輯給挪出來了用匯編指令表示。