面试可能会问到synchronized方法在抛异常时会不会解锁,本篇从编译开始看这个问题
需要知道sync有两种使用方式,一种在代码块,一种是方法上面,底层用到的lock其实是一样的,只是锁的对象不太一样
1.代码块的锁
先看两段java
public void methodSync(){
synchronized(name){
System.out.println(123);
}
}
public void methodTry(){
try {
System.out.println(123);
}finally {
}
}
编译过后查看字节码中对应的方法
methodSync
0 aload_0
1 getfield #43 <CmpTest.name : Ljava/lang/String;>
4 dup
5 astore_1
6 monitorenter
7 getstatic #47 <java/lang/System.out : Ljava/io/PrintStream;>
10 bipush 123
12 invokevirtual #53 <java/io/PrintStream.println : (I)V>
15 aload_1
16 monitorexit
17 goto 25 (+8)
20 astore_2
21 aload_1
22 monitorexit
23 aload_2
24 athrow
25 return
methodTry
0 getstatic #47 <java/lang/System.out : Ljava/io/PrintStream;>
3 bipush 123
5 invokevirtual #53 <java/io/PrintStream.println : (I)V>
8 goto 22 (+14)
11 astore_1
12 aload_1
13 invokevirtual #65 <java/lang/Exception.printStackTrace : ()V>
16 goto 22 (+6)
19 astore_2
20 aload_2
21 athrow
22 return
就说这俩像不像
在javac编译时就对指令进行了改写了
具体类在com.sun.tools.javac.jvm.Gen.java
方法为visitSynchronized
public void visitSynchronized(JCSynchronized tree) {
int limit = code.nextreg;
// Generate code to evaluate lock and save in temporary variable.
final LocalItem lockVar = makeTemp(syms.objectType);
Assert.check(code.isStatementStart());
genExpr(tree.lock, tree.lock.type).load().duplicate();
lockVar.store();
// Generate code to enter monitor.
code.emitop0(monitorenter);
code.state.lock(lockVar.reg);
// Generate code for a try statement with given body, no catch clauses
// in a new environment with the "exit-monitor" operation as finalizer.
final Env<GenContext> syncEnv = env.dup(tree, new GenContext());
syncEnv.info.finalize = new GenFinalizer() {
void gen() {
genLast();
Assert.check(syncEnv.info.gaps.length() % 2 == 0);
syncEnv.info.gaps.append(code.curCP());
}
void genLast() {
if (code.isAlive()) {
lockVar.load();
code.emitop0(monitorexit);
code.state.unlock(lockVar.reg);
}
}
};
syncEnv.info.gaps = new ListBuffer<>();
genTry(tree.body, List.nil(), syncEnv);
code.endScopes(limit);
}
看到那个genTry,和正常的try-catch代码是一样的,字节码最后到athrow之前就调用了monitorexit
上面还是java,下面就是jvm部分
1.锁创建
还是老地方templateTable_x86.cpp,汇编的难看
其实bytecodeInterpreter.cpp里面也能看到相似的可以参考
void TemplateTable::monitorenter() {
transition(atos, vtos);
// check for NULL object
//理解为监控对象不能为空
__ 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;
Register rtop = LP64_ONLY(c_rarg3) NOT_LP64(rcx);
Register rbot = LP64_ONLY(c_rarg2) NOT_LP64(rbx);
Register rmon = LP64_ONLY(c_rarg1) NOT_LP64(rdx);
// initialize entry pointer
__ xorl(rmon, rmon); // points to free slot or NULL
// find a free slot in the monitor block (result in rmon)
{
Label entry, loop, exit;
__ movptr(rtop, monitor_block_top); // points to current entry,
// starting with top-most entry
__ lea(rbot, monitor_block_bot); // points to word before bottom
// of monitor block
__ jmpb(entry);
__ bind(loop);
// check if current entry is used
__ cmpptr(Address(rtop, BasicObjectLock::obj_offset_in_bytes()), (int32_t) NULL_WORD);
// if not used then remember entry in rmon
__ cmovptr(Assembler::equal, rmon, rtop); // cmov => cmovptr
// check if current entry is for same object
//top对象如果对的上就往下一个块的代码跳了
__ cmpptr(rax, Address(rtop, BasicObjectLock::obj_offset_in_bytes()));
// if same object then stop searching
__ jccb(Assembler::equal, exit);
// otherwise advance to next entry
//锁对象地址匹配不上就要加一个地址跳回 __ bind(loop);
__ addptr(rtop, entry_size);
__ bind(entry);
// check if bottom reached
__ cmpptr(rtop, rbot);
// if not at bottom then check this entry
__ jcc(Assembler::notEqual, loop);
__ bind(exit);
}
__ testptr(rmon, rmon); // check if a slot has been found
__ jcc(Assembler::notZero, allocated); // if found, continue with that one
// allocate one if there's no free slot
//上面已经检查队列没有当前锁对象的地址了,现在放进去一个rmon
{
Label entry, loop;
// 1. compute new pointers // rsp: old expression stack top
__ movptr(rmon, monitor_block_bot); // rmon: old expression stack bottom
__ subptr(rsp, entry_size); // move expression stack top
__ subptr(rmon, entry_size); // move expression stack bottom
__ mov(rtop, rsp); // set start value for copy loop
__ movptr(monitor_block_bot, rmon); // set new monitor block bottom
__ jmp(entry);
// 2. move expression stack contents
__ bind(loop);
__ movptr(rbot, Address(rtop, entry_size)); // load expression stack
// word from old location
__ movptr(Address(rtop, 0), rbot); // and store it at new location
__ addptr(rtop, wordSize); // advance to next word
__ bind(entry);
__ cmpptr(rtop, rmon); // check if bottom reached
__ jcc(Assembler::notEqual, loop); // if not at bottom then
// copy next word
}
// call run-time routine
// rmon: 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.
__ increment(rbcp);
// store object,将rmon指向了rax
__ movptr(Address(rmon, BasicObjectLock::obj_offset_in_bytes()), rax);
//上锁了
__ lock_object(rmon);
// 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);
}
最主要是开始的地方
0 aload_0
1 getfield #43 <CmpTest.name : Ljava/lang/String;>
4 dup
5 astore_1
6 monitorenter
在调用到字节码之前,需要加锁的对象已经astore_1被放到栈顶上面了就是那个rax,在lock_object之前做的工作主要就是创建锁对象放入锁的队列,如果队列有就直接用这个锁,并且在这一步之前将锁的地址放到了rax
2.锁工作
void InterpreterMacroAssembler::lock_object(Register lock_reg) {
assert(lock_reg == LP64_ONLY(c_rarg1) NOT_LP64(rdx),
"The argument is only for looks. It must be c_rarg1");
//默认参数为关闭,重量级锁调用的call_VM
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 tmp_reg = rbx; // Will be passed to biased_locking_enter to avoid a
// problematic case where tmp_reg = no_reg.
const Register obj_reg = LP64_ONLY(c_rarg3) NOT_LP64(rcx); // Will contain the oop
const Register rklass_decode_tmp = LP64_ONLY(rscratch1) NOT_LP64(noreg);
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;
// Load object pointer into obj_reg
movptr(obj_reg, Address(lock_reg, obj_offset));
//诊断使用数值比如Integer进行加锁,开启了验证出来就会跳到最后调用重锁
//参考 https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/doc-files/ValueBased.html
if (DiagnoseSyncOnValueBasedClasses != 0) {
load_klass(tmp_reg, obj_reg, rklass_decode_tmp);
movl(tmp_reg, Address(tmp_reg, Klass::access_flags_offset()));
testl(tmp_reg, JVM_ACC_IS_VALUE_BASED_CLASS);
jcc(Assembler::notZero, slow_case);
}
//偏向锁,jdk17里面为了提升性能默认关闭
if (UseBiasedLocking) {
biased_locking_enter(lock_reg, obj_reg, swap_reg, tmp_reg, rklass_decode_tmp, false, done, &slow_case);
}
// Load immediate 1 into swap_reg %rax
//设置一个1到锁的地址,表示要加锁
movl(swap_reg, (int32_t)1);
// Load (object->mark() | 1) into swap_reg %rax
//取出对象的mark word标记位
orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
// Save (object->mark() | 1) into BasicLock's displaced header
//将锁标记进行或运算放到锁中
movptr(Address(lock_reg, mark_offset), swap_reg);
assert(lock_offset == 0,
"displaced header must be first word in BasicObjectLock");
lock();
//cas修改,没有其他线程则修改锁的地址
cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
if (PrintBiasedLockingStatistics) {
cond_inc32(Assembler::zero,
ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
}
//成功加锁就返回,等于是可重入锁,参考下面注释
jcc(Assembler::zero, done);
const int zero_bits = LP64_ONLY(7) NOT_LP64(3);
// Fast check for recursive lock.
//
// Can apply the optimization only if this is a stack lock
// allocated in this thread. For efficiency, we can focus on
// recently allocated stack locks (instead of reading the stack
// base and checking whether 'mark' points inside the current
// thread stack):
// 1) (mark & zero_bits) == 0, and
// 2) rsp <= mark < mark + os::pagesize()
//
// Warning: rsp + os::pagesize can overflow the stack base. We must
// neither apply the optimization for an inflated lock allocated
// just above the thread stack (this is why condition 1 matters)
// nor apply the optimization if the stack lock is inside the stack
// of another thread. The latter is avoided even in case of overflow
// because we have guard pages at the end of all stacks. Hence, if
// we go over the stack base and hit the stack of another thread,
// this should not be in a writeable area that could contain a
// stack lock allocated by that thread. As a consequence, a stack
// lock less than page size away from rsp is guaranteed to be
// owned by the current thread.
//
// These 3 tests can be done by evaluating the following
// expression: ((mark - rsp) & (zero_bits - os::vm_page_size())),
// assuming both stack pointer and pagesize have their
// least significant bits clear.
// NOTE: the mark is in swap_reg %rax as the result of cmpxchg
subptr(swap_reg, rsp);
//计算 ((mark - rsp) & (zero_bits - os::vm_page_size())) pagesize在linux下是4096
andptr(swap_reg, zero_bits - 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()));
}
jcc(Assembler::zero, done);
bind(slow_case);
// Call the runtime routine for slow case
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
lock_reg);
bind(done);
}
}
2.方法锁
javac生成的方法access_flag标志出了锁
如果是sync标志的方法其标志为0x0021
该标志对应的method kind为zerolocals_synchronized
在方法生成汇编时判断是否有锁,有锁直接调用TemplateInterpreterGenerator::lock_method()
判断到static后放class对象不是就放this对象到rax
从而生成了monitor对象
最后内部仍然调用的lock_object
void TemplateInterpreterGenerator::lock_method() {
// 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;
#ifdef ASSERT
{
Label L;
__ movl(rax, access_flags);
__ testl(rax, JVM_ACC_SYNCHRONIZED);
__ jcc(Assembler::notZero, L);
__ stop("method doesn't need synchronization");
__ bind(L);
}
#endif // ASSERT
// get synchronization object
{
Label done;
__ movl(rax, access_flags);
__ testl(rax, JVM_ACC_STATIC);
// get receiver (assume this is frequent case)
__ movptr(rax, Address(rlocals, Interpreter::local_offset_in_bytes(0)));
__ jcc(Assembler::zero, done);
__ load_mirror(rax, rbx);
#ifdef ASSERT
{
Label L;
__ testptr(rax, rax);
__ jcc(Assembler::notZero, L);
__ stop("synchronization object is NULL");
__ bind(L);
}
#endif // 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
// store object
__ movptr(Address(rsp, BasicObjectLock::obj_offset_in_bytes()), rax);
const Register lockreg = NOT_LP64(rdx) LP64_ONLY(c_rarg1);
__ movptr(lockreg, rsp); // object address
__ lock_object(lockreg);
}