Hotspot synchronized与volatile关键字实现(二) 源码解析

  目录

一、synchronized底层实现

1、修饰代码块字节码分析

2、InterpreterGenerator::lock_method

3、执行本地方法synchronized解锁

4、解释执行普通Java方法synchronized解锁 

二、jni_MonitorEnter / jni_MonitorExit

1、jni_MonitorEnter

2、jni_MonitorExit

三、Unsafe_MonitorEnter / Unsafe_MonitorExit / Unsafe_TryMonitorEnter

四、volatile关键字底层实现

1、volatile用法

2、volatile字节码分析

3、_getstatic / _getfield

4、_putstatic  / _putfield

5、lock指令


本篇博客继续上一篇《Hotspot synchronized与volatile关键字实现(一) 源码解析》讲解 synchronized与volatile关键字的底层实现。

一、synchronized底层实现

    上一篇中分析了修饰代码块时在解释执行和编译执行下的synchronized底层实现,我们继续探讨在修饰方法时的synchronized底层实现逻辑。

1、修饰代码块字节码分析

    测试用例如下:

public class AddTest {
    private int a;

    private static int b;

    synchronized int add(){
        a++;
        return a;
    }

    synchronized static int add2(){
        b++;
        return b;
    }

    synchronized native int addn();

    synchronized native static int add2n();

}

 将上述java代码编译后,执行javap -v命令可查看具体的字节码内容,如下:

synchronized int add();
    descriptor: ()I
    flags: ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field a:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field a:I
        10: aload_0
        11: getfield      #2                  // Field a:I
        14: ireturn
      LineNumberTable:
        line 9: 0
        line 10: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   LsynchronizedTest/AddTest;

  static synchronized int add2();
    descriptor: ()I
    flags: ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #3                  // Field b:I
         3: iconst_1
         4: iadd
         5: putstatic     #3                  // Field b:I
         8: getstatic     #3                  // Field b:I
        11: ireturn
      LineNumberTable:
        line 14: 0
        line 15: 8

  synchronized native int addn();
    descriptor: ()I
    flags: ACC_SYNCHRONIZED, ACC_NATIVE

  static synchronized native int add2n();
    descriptor: ()I
    flags: ACC_STATIC, ACC_SYNCHRONIZED, ACC_NATIVE

其中跟业务逻辑相关的指令和之前的修饰代码块时是一样的,少了monitorenter和monitorexit指令及其异常处理指令,但是方法的flags多了,ACC_STATIC表示静态方法,ACC_SYNCHRONIZED表示这是一个synchronized修饰的方法,ACC_NATIVE表示这是这个本地方法,JVM在解释执行或者编译执行时会根据flags做不同的处理。

2、InterpreterGenerator::lock_method

     普通方法解释执行和本地方法执行的Stub的生成可以参考《Hotspot 字节码执行与栈顶缓存实现 源码解析》中的AbstractInterpreterGenerator::generate_method_entry方法的讲解,如下图:

其中zerolocals表示普通的实例方法,zerolocals_synchronized表示被 synchronized关键字修饰的实例方法,native表示普通的本地方法,native_synchronized表示被 synchronized关键字修饰的本地方法。实例方法通过generate_normal_entry方法生成调用stub,本地方法通过generate_native_entry生成调用stub,这两个方法都有一个bool参数表示是否需要加锁,对generate_normal_entry而言,其入参就是截图中的synchronized变量。这两方法执行加锁的逻辑是一样,如下:

ASSERT代码块用于检测该方法的flags不包含ACC_SYNCHRONIZED,即不是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;


  // get synchronization object
  {
    const int mirror_offset = in_bytes(Klass::java_mirror_offset());
    Label done;
    //将方法的access_flags拷贝到rax中
    __ movl(rax, access_flags);
    //校验这个方法是否是静态方法
    __ testl(rax, JVM_ACC_STATIC);
    //将栈顶的执行方法调用的实例拷贝到rax中
    __ movptr(rax, Address(r14, Interpreter::local_offset_in_bytes(0)));
    //如果不是静态方法,则跳转到done
    __ jcc(Assembler::zero, done);
    //如果是静态方法,获取该Method对应的真实Klass,即pool_holder属性
    __ movptr(rax, Address(rbx, Method::const_offset()));
    __ movptr(rax, Address(rax, ConstMethod::constants_offset()));
    __ movptr(rax, Address(rax,
                           ConstantPool::pool_holder_offset_in_bytes()));
    //将Klass的java_mirror属性复制到到rax中,即某个类对应的class实例                      
    __ movptr(rax, Address(rax, mirror_offset));
    __ bind(done);
  }

  //将rsp往下,即低地址端移动entry_size,即一个BasicObjectLock的大小
  __ subptr(rsp, entry_size); // add space for a monitor entry
  //将rsp地址写入栈帧中monitor_block_top地址
  __ movptr(monitor_block_top, rsp);  // set new monitor block top
  //将rax即跟锁关联的对象保存到BasicObjectLock的obj属性
  __ movptr(Address(rsp, BasicObjectLock::obj_offset_in_bytes()), rax);
  //将rsp地址拷贝到c_rarg1,即BasicObjectLock实例的地址
  __ movptr(c_rarg1, rsp); // object address
  //调用lock_object加锁
  __ lock_object(c_rarg1);
}

实现加锁的lock_object方法跟 synchronized修饰代码块是调用方法时一样的,都是InterpreterMacroAssembler::lock_object方法,参考其调用链如下:

其中TemplateTable::monitorenter就是monitorenter字节码指令的实现。

3、执行本地方法synchronized解锁

      Java代码中执行本地方法时比较特殊,具体逻辑可以参考《Hotspot 本地方法绑定与执行 源码解析》,本地方法执行完成返回到上一次Java代码的调用的逻辑也是在generate_native_entry中,在返回前就需要执行synchronized解锁,其实现如下:

 // do unlocking if necessary
  {
    Label L;
    //获取方法的access_flags
    __ movl(t, Address(method, Method::access_flags_offset()));
    __ testl(t, JVM_ACC_SYNCHRONIZED);
    //如果不是synchronized方法跳转到L
    __ jcc(Assembler::zero, L);
    //如果是synchronized方法执行解锁
    {
      Label unlock;
      // BasicObjectLock will be first in list, since this is a
      // synchronized method. However, need to check that the object
      // has not been unlocked by an explicit monitorexit bytecode.
      //取出放在Java栈帧头部的BasicObjectLock
      const Address monitor(rbp,
                            (intptr_t)(frame::interpreter_frame_initial_sp_offset *
                                       wordSize - sizeof(BasicObjectLock)));

      //将monitor的地址即关联的BasicObjectLock的地址放入c_rarg1中
      __ lea(c_rarg1, monitor); // address of first monitor
      //将BasicObjectLock的obj属性赋值到t中
      __ movptr(t, Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()));
      //判断obj属性是否为空
      __ testptr(t, t);
      //如果不为空,则跳转到unlock
      __ jcc(Assembler::notZero, unlock);

      //如果为空,说明未上锁,则抛出异常
      __ MacroAssembler::call_VM(noreg,
                                 CAST_FROM_FN_PTR(address,
                   InterpreterRuntime::throw_illegal_monitor_state_exception));
      __ should_not_reach_here();

      __ bind(unlock);
      //解锁
      __ unlock_object(c_rarg1);
    }
    __ bind(L);
  }

上述unlock_object方法跟修饰代码块解锁时调用InterpreterMacroAssembler::unlock_object方法,其调用链如下:

其中TemplateTable::monitorexit方法就是monitorexit指令的实现,remove_activation方法用于实现return指令,抛出异常的stub等。

4、解释执行普通Java方法synchronized解锁 

   普通Java方法执行完成后需要通过return系列指令返回到方法的调用栈帧,synchronized解锁就是在return系列指令中完成的。return系列指令一共有7个,其底层实现都是同一个方法,参考TemplateTable::initialize方法的定义,如下图:

ireturn表示返回一个int值,lreturn表示返回一个long值,freturn表示返回一个float值, dreturn表示返回一个double值,areturn表示返回一个对象引用,return表示返回void,除上述6个外OpenJDK还增加了一个_return_register_finalizer,跟return一样都是返回void,不同的是如果目标类实现了finalize方法则会注册对应的Finalizer。_return方法的实现如下:

void TemplateTable::_return(TosState state) {
  transition(state, state);
  assert(_desc->calls_vm(),
         "inconsistent calls_vm information"); // call in remove_activation
  
  //如果当前字节码是_return_register_finalizer,这个是OpenJDK特有的
  if (_desc->bytecode() == Bytecodes::_return_register_finalizer) {
    assert(state == vtos, "only valid state");
    __ movptr(c_rarg1, aaddress(0));
    //c_rarg1保存了方法调用的oop,获取其klass
    __ load_klass(rdi, c_rarg1);
    //获取类的access_flags,判断其是否实现了finalizer方法
    __ movl(rdi, Address(rdi, Klass::access_flags_offset()));
    __ testl(rdi, JVM_ACC_HAS_FINALIZER);
    Label skip_register_finalizer;
    //如果未实现,则跳转到skip_register_finalizer
    __ jcc(Assembler::zero, skip_register_finalizer);
    //如果实现了,则执行register_finalizer方法
    __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::register_finalizer), c_rarg1);

    __ bind(skip_register_finalizer);
  }

  if (state == itos) {
    //如果返回值是int,则执行narrow方法,会根据方法的返回值类型做特殊处理,因为在JVM中char,byte,boolean三种
    //都是作为int处理的,如果返回值类型是上述的三种之一则将返回值由4个字节调整成对
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值