死磕Synchronized底层实现--偏向锁

本文为synchronized系列第二篇。主要内容为分析偏向锁的实现。java

偏向锁的诞生背景和基本原理在上文中已经讲过了,强烈建议在有看过上篇文章的基础下阅读本文c++

更多文章见我的博客:github.com/farmerjohng…git

本系列文章将对HotSpot的synchronized锁实现进行全面分析,内容包括偏向锁、轻量级锁、重量级锁的加锁、解锁、锁升级流程的原理及源码分析,但愿给在研究synchronized路上的同窗一些帮助。主要包括如下几篇文章:github

死磕Synchronized底层实现--概论安全

死磕Synchronized底层实现--偏向锁多线程

死磕Synchronized底层实现--轻量级锁并发

死磕Synchronized底层实现--重量级锁app

本文将分为几块内容:jvm

1.偏向锁的入口ide

2.偏向锁的获取流程

3.偏向锁的撤销流程

4.偏向锁的释放流程

5.偏向锁的批量重偏向和批量撤销

本文分析的JVM版本是JVM8,具体版本号以及代码能够在这里看到。

偏向锁入口

目前网上的不少文章,关于偏向锁源码入口都找错地方了,致使我以前对于偏向锁的不少逻辑一直想不通,走了不少弯路。

synchronized分为synchronized代码块和synchronized方法,其底层获取锁的逻辑都是同样的,本文讲解的是synchronized代码块的实现。上篇文章也说过,synchronized代码块是由monitorentermonitorexit两个指令实现的。

关于HotSpot虚拟机中获取锁的入口,网上不少文章要么给出的方法入口为interpreterRuntime.cpp#monitorenter,要么给出的入口为bytecodeInterpreter.cpp#1816。包括占小狼的这篇文章关于锁入口的位置说法也是有问题的(固然文章仍是很好的,在我刚开始研究synchronized的时候,小狼哥的这篇文章给了我不少帮助)。

要找锁的入口,确定是要在源码中找到对monitorenter指令解析的地方。在HotSpot的中有两处地方对monitorenter指令进行解析:一个是在bytecodeInterpreter.cpp#1816 ,另外一个是在templateTable_x86_64.cpp#3667

前者是JVM中的字节码解释器(bytecodeInterpreter),用C++实现了每条JVM指令(如monitorenterinvokevirtual等),其优势是实现相对简单且容易理解,缺点是执行慢。后者是模板解释器(templateInterpreter),其对每一个指令都写了一段对应的汇编代码,启动时将每一个指令与对应汇编代码入口绑定,能够说是效率作到了极致。模板解释器的实现能够看这篇文章在研究的过程当中也请教过文章做者‘汪先生’一些问题,这里感谢一下。

在HotSpot中,只用到了模板解释器,字节码解释器根本就没用到,R大的读书笔记中说的很清楚了,你们能够看看,这里再也不赘述。

因此montorenter的解析入口在模板解释器中,其代码位于templateTable_x86_64.cpp#3667。经过调用路径:templateTable_x86_64#monitorenter->interp_masm_x86_64#lock_object进入到偏向锁入口macroAssembler_x86#biased_locking_enter,在这里你们能够看到会生成对应的汇编代码。须要注意的是,不是说每次解析monitorenter指令都会调用biased_locking_enter,而是只会在JVM启动的时候调用该方法生成汇编代码,以后对指令的解析是经过直接执行汇编代码。

其实bytecodeInterpreter的逻辑和templateInterpreter的逻辑是大同小异的,由于templateInterpreter中都是汇编代码,比较晦涩,因此看bytecodeInterpreter的实现会便于理解一点。但这里有个坑,在jdk8u以前,bytecodeInterpreter并无实现偏向锁的逻辑。我以前看的JDK8-87ee5ee27509这个版本就没有实现偏向锁的逻辑,致使我看了好久都没看懂。在这个commit中对bytecodeInterpreter加入了偏向锁的支持,我大体了看了下和templateInterpreter对比除了栈结构不一样外,其余逻辑大体相同,因此下文就按bytecodeInterpreter中的代码对偏向锁逻辑进行讲解templateInterpreter的汇编代码讲解能够看这篇文章,其实汇编源码中都有英文注释,了解了汇编几个基本指令的做用再结合注释理解起来也不是很难。

偏向锁获取流程

下面开始偏向锁获取流程分析,代码在bytecodeInterpreter.cpp#1816注意本文代码都有所删减

CASE(_monitorenter): {
  // lockee 就是锁对象
  oop lockee = STACK_OBJECT(-1);
  // derefing's lockee ought to provoke implicit null check
  CHECK_NULL(lockee);
  // code 1:找到一个空闲的Lock Record
  BasicObjectLock* limit = istate->monitor_base();
  BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
  BasicObjectLock* entry = NULL;
  while (most_recent != limit ) {
    if (most_recent->obj() == NULL) entry = most_recent;
    else if (most_recent->obj() == lockee) break;
    most_recent++;
  }
  //entry不为null,表明还有空闲的Lock Record
  if (entry != NULL) {
    // code 2:将Lock Record的obj指针指向锁对象
    entry->set_obj(lockee);
    int success = false;
    uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
	// markoop即对象头的mark word
    markOop mark = lockee->mark();
    intptr_t hash = (intptr_t) markOopDesc::no_hash;
    // code 3:若是锁对象的mark word的状态是偏向模式
    if (mark->has_bias_pattern()) {
      uintptr_t thread_ident;
      uintptr_t anticipated_bias_locking_value;
      thread_ident = (uintptr_t)istate->thread();
     // code 4:这里有几步操做,下文分析
      anticipated_bias_locking_value =
        (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
        ~((uintptr_t) markOopDesc::age_mask_in_place);
	 // code 5:若是偏向的线程是本身且epoch等于class的epoch
      if  (anticipated_bias_locking_value == 0) {
        // already biased towards this thread, nothing to do
        if (PrintBiasedLockingStatistics) {
          (* BiasedLocking::biased_lock_entry_count_addr())++;
        }
        success = true;
      }
       // code 6:若是偏向模式关闭,则尝试撤销偏向锁
      else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
        markOop header = lockee->klass()->prototype_header();
        if (hash != markOopDesc::no_hash) {
          header = header->copy_set_hash(hash);
        }
        // 利用CAS操做将mark word替换为class中的mark word
        if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
          if (PrintBiasedLockingStatistics)
            (*BiasedLocking::revoked_lock_entry_count_addr())++;
        }
      }
         // code 7:若是epoch不等于class中的epoch,则尝试重偏向
      else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
        // 构造一个偏向当前线程的mark word
        markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
        if (hash != markOopDesc::no_hash) {
          new_header = new_header->copy_set_hash(hash);
        }
        // CAS替换对象头的mark word 
        if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
          if (PrintBiasedLockingStatistics)
            (* BiasedLocking::rebiased_lock_entry_count_addr())++;
        }
        else {
          // 重偏向失败,表明存在多线程竞争,则调用monitorenter方法进行锁升级
          CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
        }
        success = true;
      }
      else {
         // 走到这里说明当前要么偏向别的线程,要么是匿名偏向(即没有偏向任何线程)
       	// code 8:下面构建一个匿名偏向的mark word,尝试用CAS指令替换掉锁对象的mark word
        markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |(uintptr_t)markOopDesc::age_mask_in_place |epoch_mask_in_place));
        if (hash != markOopDesc::no_hash) {
          header = header->copy_set_hash(hash);
        }
        markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
        // debugging hint
        DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
        if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
           // CAS修改为功
          if (PrintBiasedLockingStatistics)
            (* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
        }
        else {
          // 若是修改失败说明存在多线程竞争,因此进入monitorenter方法
          CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
        }
        success = true;
      }
    }

    // 若是偏向线程不是当前线程或没有开启偏向模式等缘由都会致使success==false
    if (!success) {
      // 轻量级锁的逻辑
      //code 9: 构造一个无锁状态的Displaced Mark Word,并将Lock Record的lock指向它
      markOop displaced = lockee->mark()->set_unlocked();
      entry->lock()->set_displaced_header(displaced);
      //若是指定了-XX:+UseHeavyMonitors,则call_vm=true,表明禁用偏向锁和轻量级锁
      bool call_vm = UseHeavyMonitors;
      // 利用CAS将对象头的mark word替换为指向Lock Record的指针
      if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
        // 判断是否是锁重入
        if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {		//code 10: 若是是锁重入,则直接将Displaced Mark Word设置为null
          entry->lock()->set_displaced_header(NULL);
        } else {
          CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
        }
      }
    }
    UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
  } else {
    // lock record不够,从新执行
    istate->set_msg(more_monitors);
    UPDATE_PC_AND_RETURN(0); // Re-execute
  }
}
复制代码

再回顾下对象头中mark word的格式:

image

JVM中的每一个类也有一个相似mark word的prototype_header,用来标记该class的epoch和偏向开关等信息。上面的代码中lockee->klass()->prototype_header()即获取class的prototype_header。

code 1,从当前线程的栈中找到一个空闲的Lock Record即代码中的BasicObjectLock,下文都用Lock Record代指),判断Lock Record是否空闲的依据是其obj字段 是否为null。注意这里是按内存地址从低往高找到最后一个可用的Lock Record,换而言之,就是找到内存地址最高的可用Lock Record

code 2,获取到Lock Record后,首先要作的就是为其obj字段赋值。

code 3,判断锁对象的mark word是不是偏向模式,即低3位是否为101。

code 4,这里有几步位运算的操做anticipated_bias_locking_value = (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) & ​ ~((uintptr_t) markOopDesc::age_mask_in_place); 这个位运算能够分为3个部分。

第一部分((uintptr_t)lockee->klass()->prototype_header() | thread_ident) 将当前线程id和类的prototype_header相或,这样获得的值为(当前线程id + prototype_header中的(epoch + 分代年龄 + 偏向锁标志 + 锁标志位)),注意prototype_header的分代年龄那4个字节为0

第二部分 ^ (uintptr_t)mark 将上面计算获得的结果与锁对象的markOop进行异或,相等的位所有被置为0,只剩下不相等的位。

第三部分 & ~((uintptr_t) markOopDesc::age_mask_in_place) markOopDesc::age_mask_in_place为...0001111000,取反后,变成了...1110000111,除了分代年龄那4位,其余位全为1;将取反后的结果再与上面的结果相与,将上面异或获得的结果中分代年龄给忽略掉。

code 5anticipated_bias_locking_value==0表明偏向的线程是当前线程且mark word的epoch等于class的epoch,这种状况下什么都不用作。

code 6(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0表明class的prototype_header或对象的mark word中偏向模式是关闭的,又由于能走到这已经经过了mark->has_bias_pattern()判断,即对象的mark word中偏向模式是开启的,那也就是说class的prototype_header不是偏向模式。

而后利用CAS指令Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark撤销偏向锁,咱们知道CAS会有几个参数,1是预期的原值,2是预期修改后的值 ,3是要修改的对象,与之对应,cmpxchg_ptr方法第一个参数是预期修改后的值,第2个参数是修改的对象,第3个参数是预期原值,方法返回实际原值,若是等于预期原值则说明修改为功。

code 7,若是epoch已过时,则须要重偏向,利用CAS指令将锁对象的mark word替换为一个偏向当前线程且epoch为类的epoch的新的mark word

code 8,CAS将偏向线程改成当前线程,若是当前是匿名偏向则能修改为功,不然进入锁升级的逻辑。

code 9,这一步已是轻量级锁的逻辑了。从上图的mark word的格式能够看到,轻量级锁中mark word存的是指向Lock Record的指针。这里构造一个无锁状态的mark word,而后存储到Lock RecordLock Record的格式能够看第一篇文章)。设置mark word是无锁状态的缘由是:轻量级锁解锁时是将对象头的mark word设置为Lock Record中的Displaced Mark Word,因此建立时设置为无锁状态,解锁时直接用CAS替换就行了。

code 10, 若是是锁重入,则将Lock RecordDisplaced Mark Word设置为null,起到一个锁重入计数的做用。

以上是偏向锁加锁的流程(包括部分轻量级锁的加锁流程),若是当前锁已偏向其余线程||epoch值过时||偏向模式关闭||获取偏向锁的过程当中存在并发冲突,都会进入到InterpreterRuntime::monitorenter方法, 在该方法中会对偏向锁撤销和升级。

偏向锁的撤销

这里说的撤销是指在获取偏向锁的过程由于不知足条件致使要将锁对象改成非偏向锁状态;释放是指退出同步块时的过程,释放锁的逻辑会在下一小节阐述。请读者注意本文中撤销与释放的区别

若是获取偏向锁失败会进入到InterpreterRuntime::monitorenter方法

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(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");
  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
复制代码

能够看到若是开启了JVM偏向锁,那会进入到ObjectSynchronizer::fast_enter方法中。

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) ;
}
复制代码

若是是正常的Java线程,会走上面的逻辑进入到BiasedLocking::revoke_and_rebias方法,若是是VM线程则会走到下面的BiasedLocking::revoke_at_safepoint。咱们主要看BiasedLocking::revoke_and_rebias方法。这个方法的主要做用像它的方法名:撤销或者重偏向,第一个参数封装了锁对象和当前线程,第二个参数表明是否容许重偏向,这里是true。

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
  assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");
    
  markOop mark = obj->mark();
  if (mark->is_biased_anonymously() && !attempt_rebias) {
     //若是是匿名偏向且attempt_rebias==false会走到这里,如锁对象的hashcode方法被调用会出现这种状况,须要撤销偏向锁。
    markOop biased_value       = mark;
    markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
    markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
    if (res_mark == biased_value) {
      return BIAS_REVOKED;
    }
  } else if (mark->has_bias_pattern()) {
    // 锁对象开启了偏向模式会走到这里
    Klass* k = obj->klass();
    markOop prototype_header = k->prototype_header();
    //code 1: 若是对应class关闭了偏向模式
    if (!prototype_header->has_bias_pattern()) {
      markOop biased_value       = mark;
      markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
      assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");
      return BIAS_REVOKED;
    //code2: 若是epoch过时
    } else if (prototype_header->bias_epoch() != mark->bias_epoch()) {
      if (attempt_rebias) {
        assert(THREAD->is_Java_thread(), "");
        markOop biased_value       = mark;
        markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
        markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED_AND_REBIASED;
        }
      } else {
        markOop biased_value       = mark;
        markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
        markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED;
        }
      }
    }
  }
  //code 3:批量重偏向与批量撤销的逻辑
  HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
  if (heuristics == HR_NOT_BIASED) {
    return NOT_BIASED;
  } else if (heuristics == HR_SINGLE_REVOKE) {
    //code 4:撤销单个线程
    Klass *k = obj->klass();
    markOop prototype_header = k->prototype_header();
    if (mark->biased_locker() == THREAD &&
        prototype_header->bias_epoch() == mark->bias_epoch()) {
      // 走到这里说明须要撤销的是偏向当前线程的锁,当调用Object#hashcode方法时会走到这一步
      // 由于只要遍历当前线程的栈就行了,因此不须要等到safepoint再撤销。
      ResourceMark rm;
      if (TraceBiasedLocking) {
        tty->print_cr("Revoking bias by walking my own stack:");
      }
      BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);
      ((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
      assert(cond == BIAS_REVOKED, "why not?");
      return cond;
    } else {
      // 下面代码最终会在VM线程中的safepoint调用revoke_bias方法
      VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
      VMThread::execute(&revoke);
      return revoke.status_code();
    }
  }
	
  assert((heuristics == HR_BULK_REVOKE) ||
         (heuristics == HR_BULK_REBIAS), "?");
   //code5:批量撤销、批量重偏向的逻辑
  VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
                                (heuristics == HR_BULK_REBIAS),
                                attempt_rebias);
  VMThread::execute(&bulk_revoke);
  return bulk_revoke.status_code();
}

复制代码

会走到该方法的逻辑有不少,咱们只分析最多见的状况:假设锁已经偏向线程A,这时B线程尝试得到锁。

上面的code 1code 2B线程都不会走到,最终会走到code 4处,若是要撤销的锁偏向的是当前线程则直接调用revoke_bias撤销偏向锁,不然会将该操做push到VM Thread中等到safepoint的时候再执行。

关于VM Thread这里介绍下:在JVM中有个专门的VM Thread,该线程会源源不断的从VMOperationQueue中取出请求,好比GC请求。对于须要safepoint的操做(VM_Operationevaluate_at_safepoint返回true)必需要等到全部的Java线程进入到safepoint才开始执行。 关于safepoint能够参考下这篇文章

接下来咱们着重分析下revoke_bias方法。第一个参数为锁对象,第二、3个参数为都为false

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) {
  markOop mark = obj->mark();
  // 若是没有开启偏向模式,则直接返回NOT_BIASED
  if (!mark->has_bias_pattern()) {
    ...
    return BiasedLocking::NOT_BIASED;
  }

  uint age = mark->age();
  // 构建两个mark word,一个是匿名偏向模式(101),一个是无锁模式(001)
  markOop   biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
  markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);

  ...

  JavaThread* biased_thread = mark->biased_locker();
  if (biased_thread == NULL) {
     // 匿名偏向。当调用锁对象的hashcode()方法可能会致使走到这个逻辑
     // 若是不容许重偏向,则将对象的mark word设置为无锁模式
    if (!allow_rebias) {
      obj->set_mark(unbiased_prototype);
    }
    ...
    return BiasedLocking::BIAS_REVOKED;
  }

  // code 1:判断偏向线程是否还存活
  bool thread_is_alive = false;
  // 若是当前线程就是偏向线程 
  if (requesting_thread == biased_thread) {
    thread_is_alive = true;
  } else {
     // 遍历当前jvm的全部线程,若是能找到,则说明偏向的线程还存活
    for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) {
      if (cur_thread == biased_thread) {
        thread_is_alive = true;
        break;
      }
    }
  }
  // 若是偏向的线程已经不存活了
  if (!thread_is_alive) {
    // 容许重偏向则将对象mark word设置为匿名偏向状态,不然设置为无锁状态
    if (allow_rebias) {
      obj->set_mark(biased_prototype);
    } else {
      obj->set_mark(unbiased_prototype);
    }
    ...
    return BiasedLocking::BIAS_REVOKED;
  }

  // 线程还存活则遍历线程栈中全部的Lock Record
  GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
  BasicLock* highest_lock = NULL;
  for (int i = 0; i < cached_monitor_info->length(); i++) {
    MonitorInfo* mon_info = cached_monitor_info->at(i);
    // 若是能找到对应的Lock Record说明偏向的线程还在执行同步代码块中的代码
    if (mon_info->owner() == obj) {
      ...
      // 须要升级为轻量级锁,直接修改偏向线程栈中的Lock Record。为了处理锁重入的case,在这里将Lock Record的Displaced Mark Word设置为null,第一个Lock Record会在下面的代码中再处理
      markOop mark = markOopDesc::encode((BasicLock*) NULL);
      highest_lock = mon_info->lock();
      highest_lock->set_displaced_header(mark);
    } else {
      ...
    }
  }
  if (highest_lock != NULL) {
    // 修改第一个Lock Record为无锁状态,而后将obj的mark word设置为执行该Lock Record的指针
    highest_lock->set_displaced_header(unbiased_prototype);
    obj->release_set_mark(markOopDesc::encode(highest_lock));
    ...
  } else {
    // 走到这里说明偏向线程已经不在同步块中了
    ...
    if (allow_rebias) {
       //设置为匿名偏向状态
      obj->set_mark(biased_prototype);
    } else {
      // 将mark word设置为无锁状态
      obj->set_mark(unbiased_prototype);
    }
  }

  return BiasedLocking::BIAS_REVOKED;
}
复制代码

须要注意下,当调用锁对象的Object#hashSystem.identityHashCode()方法会致使该对象的偏向锁或轻量级锁升级。这是由于在Java中一个对象的hashcode是在调用这两个方法时才生成的,若是是无锁状态则存放在mark word中,若是是重量级锁则存放在对应的monitor中,而偏向锁是没有地方能存放该信息的,因此必须升级。具体能够看这篇文章hashcode()方法对偏向锁的影响小节(注意:该文中对于偏向锁的加锁描述有些错误),另外我也向该文章做者请教过一些问题,他很热心的回答了我,在此感谢一下!

言归正传,revoke_bias方法逻辑:

  1. 查看偏向的线程是否存活,若是已经不存活了,则直接撤销偏向锁。JVM维护了一个集合存放全部存活的线程,经过遍历该集合判断某个线程是否存活。
  2. 偏向的线程是否还在同步块中,若是不在了,则撤销偏向锁。咱们回顾一下偏向锁的加锁流程:每次进入同步块(即执行monitorenter)的时候都会以从高往低的顺序在栈中找到第一个可用的Lock Record,将其obj字段指向锁对象。每次解锁(即执行monitorexit)的时候都会将最低的一个相关Lock Record移除掉。因此能够经过遍历线程栈中的Lock Record来判断线程是否还在同步块中。
  3. 将偏向线程全部相关Lock RecordDisplaced Mark Word设置为null,而后将最高位的Lock RecordDisplaced Mark Word 设置为无锁状态,最高位的Lock Record也就是第一次得到锁时的Lock Record(这里的第一次是指重入获取锁时的第一次),而后将对象头指向最高位的Lock Record,这里不须要用CAS指令,由于是在safepoint。 执行完后,就升级成了轻量级锁。原偏向线程的全部Lock Record都已经变成轻量级锁的状态。这里若是看不明白,请回顾上篇文章的轻量级锁加锁过程。

偏向锁的释放

偏向锁的释放入口在bytecodeInterpreter.cpp#1923

CASE(_monitorexit): {
  oop lockee = STACK_OBJECT(-1);
  CHECK_NULL(lockee);
  // derefing's lockee ought to provoke implicit null check
  // find our monitor slot
  BasicObjectLock* limit = istate->monitor_base();
  BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
  // 从低往高遍历栈的Lock Record
  while (most_recent != limit ) {
    // 若是Lock Record关联的是该锁对象
    if ((most_recent)->obj() == lockee) {
      BasicLock* lock = most_recent->lock();
      markOop header = lock->displaced_header();
      // 释放Lock Record
      most_recent->set_obj(NULL);
      // 若是是偏向模式,仅仅释放Lock Record就行了。不然要走轻量级锁or重量级锁的释放流程
      if (!lockee->mark()->has_bias_pattern()) {
        bool call_vm = UseHeavyMonitors;
        // header!=NULL说明不是重入,则须要将Displaced Mark Word CAS到对象头的Mark Word
        if (header != NULL || call_vm) {
          if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
            // CAS失败或者是重量级锁则会走到这里,先将obj还原,而后调用monitorexit方法
            most_recent->set_obj(lockee);
            CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
          }
        }
      }
      //执行下一条命令
      UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
    }
    //处理下一条Lock Record
    most_recent++;
  }
  // Need to throw illegal monitor state exception
  CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
  ShouldNotReachHere();
}
复制代码

上面的代码结合注释理解起来应该不难,偏向锁的释放很简单,只要将对应Lock Record释放就行了,而轻量级锁则须要将Displaced Mark Word替换到对象头的mark word中。若是CAS失败或者是重量级锁则进入到InterpreterRuntime::monitorexit方法中。该方法会在轻量级与重量级锁的文章中讲解。

批量重偏向和批量撤销

批量重偏向和批量撤销的背景能够看上篇文章,相关实如今BiasedLocking::revoke_and_rebias中:

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
  ...
  //code 1:重偏向的逻辑
  HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
  // 非重偏向的逻辑
  ...
      
  assert((heuristics == HR_BULK_REVOKE) ||
         (heuristics == HR_BULK_REBIAS), "?");	
   //code 2:批量撤销、批量重偏向的逻辑
  VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
                                (heuristics == HR_BULK_REBIAS),
                                attempt_rebias);
  VMThread::execute(&bulk_revoke);
  return bulk_revoke.status_code();
}
复制代码

在每次撤销偏向锁的时候都经过update_heuristics方法记录下来,以类为单位,当某个类的对象撤销偏向次数达到必定阈值的时候JVM就认为该类不适合偏向模式或者须要从新偏向另外一个对象,update_heuristics就会返回HR_BULK_REVOKEHR_BULK_REBIAS。进行批量撤销或批量重偏向。

先看update_heuristics方法。

static HeuristicsResult update_heuristics(oop o, bool allow_rebias) {
  markOop mark = o->mark();
  //若是不是偏向模式直接返回
  if (!mark->has_bias_pattern()) {
    return HR_NOT_BIASED;
  }
 
  // 锁对象的类
  Klass* k = o->klass();
  // 当前时间
  jlong cur_time = os::javaTimeMillis();
  // 该类上一次批量撤销的时间
  jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time();
  // 该类偏向锁撤销的次数
  int revocation_count = k->biased_lock_revocation_count();
  // BiasedLockingBulkRebiasThreshold是重偏向阈值(默认20),BiasedLockingBulkRevokeThreshold是批量撤销阈值(默认40),BiasedLockingDecayTime是开启一次新的批量重偏向距离上次批量重偏向的后的延迟时间,默认25000。也就是开启批量重偏向后,通过了一段较长的时间(>=BiasedLockingDecayTime),撤销计数器才超过阈值,那咱们会重置计数器。
  if ((revocation_count >= BiasedLockingBulkRebiasThreshold) &&
      (revocation_count <  BiasedLockingBulkRevokeThreshold) &&
      (last_bulk_revocation_time != 0) &&
      (cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) {
    // This is the first revocation we've seen in a while of an
    // object of this type since the last time we performed a bulk
    // rebiasing operation. The application is allocating objects in
    // bulk which are biased toward a thread and then handing them
    // off to another thread. We can cope with this allocation
    // pattern via the bulk rebiasing mechanism so we reset the
    // klass's revocation count rather than allow it to increase
    // monotonically. If we see the need to perform another bulk
    // rebias operation later, we will, and if subsequently we see
    // many more revocation operations in a short period of time we
    // will completely disable biasing for this type.
    k->set_biased_lock_revocation_count(0);
    revocation_count = 0;
  }

  // 自增撤销计数器
  if (revocation_count <= BiasedLockingBulkRevokeThreshold) {
    revocation_count = k->atomic_incr_biased_lock_revocation_count();
  }
  // 若是达到批量撤销阈值则返回HR_BULK_REVOKE
  if (revocation_count == BiasedLockingBulkRevokeThreshold) {
    return HR_BULK_REVOKE;
  }
  // 若是达到批量重偏向阈值则返回HR_BULK_REBIAS
  if (revocation_count == BiasedLockingBulkRebiasThreshold) {
    return HR_BULK_REBIAS;
  }
  // 没有达到阈值则撤销单个对象的锁
  return HR_SINGLE_REVOKE;
}
复制代码

当达到阈值的时候就会经过VM 线程在safepoint调用bulk_revoke_or_rebias_at_safepoint, 参数bulk_rebias若是是true表明是批量重偏向不然为批量撤销。attempt_rebias_of_object表明对操做的锁对象o是否运行重偏向,这里是true

static BiasedLocking::Condition bulk_revoke_or_rebias_at_safepoint(oop o, bool bulk_rebias, bool attempt_rebias_of_object, JavaThread* requesting_thread) {
  ...
  jlong cur_time = os::javaTimeMillis();
  o->klass()->set_last_biased_lock_bulk_revocation_time(cur_time);


  Klass* k_o = o->klass();
  Klass* klass = k_o;

  if (bulk_rebias) {
    // 批量重偏向的逻辑
    if (klass->prototype_header()->has_bias_pattern()) {
      // 自增前类中的的epoch
      int prev_epoch = klass->prototype_header()->bias_epoch();
      // code 1:类中的epoch自增
      klass->set_prototype_header(klass->prototype_header()->incr_bias_epoch());
      int cur_epoch = klass->prototype_header()->bias_epoch();

      // code 2:遍历全部线程的栈,更新类型为该klass的全部锁实例的epoch
      for (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {
        GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);
        for (int i = 0; i < cached_monitor_info->length(); i++) {
          MonitorInfo* mon_info = cached_monitor_info->at(i);
          oop owner = mon_info->owner();
          markOop mark = owner->mark();
          if ((owner->klass() == k_o) && mark->has_bias_pattern()) {
            // We might have encountered this object already in the case of recursive locking
            assert(mark->bias_epoch() == prev_epoch || mark->bias_epoch() == cur_epoch, "error in bias epoch adjustment");
            owner->set_mark(mark->set_bias_epoch(cur_epoch));
          }
        }
      }
    }

    // 接下来对当前锁对象进行重偏向
    revoke_bias(o, attempt_rebias_of_object && klass->prototype_header()->has_bias_pattern(), true, requesting_thread);
  } else {
    ...

    // code 3:批量撤销的逻辑,将类中的偏向标记关闭,markOopDesc::prototype()返回的是一个关闭偏向模式的prototype
    klass->set_prototype_header(markOopDesc::prototype());

    // code 4:遍历全部线程的栈,撤销该类全部锁的偏向
    for (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {
      GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);
      for (int i = 0; i < cached_monitor_info->length(); i++) {
        MonitorInfo* mon_info = cached_monitor_info->at(i);
        oop owner = mon_info->owner();
        markOop mark = owner->mark();
        if ((owner->klass() == k_o) && mark->has_bias_pattern()) {
          revoke_bias(owner, false, true, requesting_thread);
        }
      }
    }

    // 撤销当前锁对象的偏向模式
    revoke_bias(o, false, true, requesting_thread);
  }

  ...
  
  BiasedLocking::Condition status_code = BiasedLocking::BIAS_REVOKED;

  if (attempt_rebias_of_object &&
      o->mark()->has_bias_pattern() &&
      klass->prototype_header()->has_bias_pattern()) {
    // 构造一个偏向请求线程的mark word
    markOop new_mark = markOopDesc::encode(requesting_thread, o->mark()->age(),
                                           klass->prototype_header()->bias_epoch());
    // 更新当前锁对象的mark word
    o->set_mark(new_mark);
    status_code = BiasedLocking::BIAS_REVOKED_AND_REBIASED;
    ...
  }

  ...

  return status_code;
}

复制代码

该方法分为两个逻辑:批量重偏向和批量撤销。

先看批量重偏向,分为两步:

code 1 将类中的撤销计数器自增1,以后当该类已存在的实例得到锁时,就会尝试重偏向,相关逻辑在偏向锁获取流程小节中。

code 2 处理当前正在被使用的锁对象,经过遍历全部存活线程的栈,找到全部正在使用的偏向锁对象,而后更新它们的epoch值。也就是说不会重偏向正在使用的锁,不然会破坏锁的线程安全性。

批量撤销逻辑以下:

code 3将类的偏向标记关闭,以后当该类已存在的实例得到锁时,就会升级为轻量级锁;该类新分配的对象的mark word则是无锁模式。

code 4处理当前正在被使用的锁对象,经过遍历全部存活线程的栈,找到全部正在使用的偏向锁对象,而后撤销偏向锁。

相关文章
相关标签/搜索