jvm(7) jdk17中的synchronized 和monitorenter 源码

面试可能会问到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);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容