源码解析-偏向锁撤销流程解读

一、单个偏向锁的撤销

源码链接:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/runtime/biasedLocking.cpp#l146

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;
  }

  // 判断偏向线程是否还存活
  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
      // 先处理所有锁重入的情况,轻量级锁的 displaced mark word 为 NULL,表示锁重入
      markOop mark = markOopDesc::encode((BasicLock*) NULL);
      highest_lock = mon_info->lock();
      highest_lock->set_displaced_header(mark);
    } else {
      ...
    }
  }
  if (highest_lock != NULL) { // highest_lock 如果非空,则它是最早关联该锁的 lock record
    // 这个 lock record 是线程彻底退出该锁的最后一个 lock record
    // 所以要,设置 lock record 的 displaced mark word 为无锁状态的 mark word
    // 并让锁对象的 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;
}

简单总结下偏向撤销的流程:

  1. 偏向所有者没有正在持有待撤销的偏向锁,如果允许重偏向则偏向锁变可偏向锁;否则偏向锁撤销为无锁。
  2. 偏向所有者正在持有该偏向锁,撤销该锁为偏向所有者正在持有的轻量级锁。

细节补充:
如何判断偏向所有者没有正在持有该偏向锁?
分两步,首先判断偏向所有者是否还活着,如果还活着,则遍历它的栈,看是否能找到关联该锁的锁记录,如果找到,则正在持有,如果没找到,则没有持有。(遍历过程在一个安全点执行,此时偏向所有者被阻塞。)

偏向所有者正在持有该偏向锁,如何将其撤销为轻量级锁?
遍历偏向所有者的栈,修改与该锁关联的所有锁记录,让偏向所有者以为它对该对象加的就是轻量级锁。

源码中的 highest_lock,为什么说是最早关联偏向锁的锁记录呢?
首先,锁记录在栈里是连续存放的。
请求获取锁时,按照从低地址到高地址的顺序,找在已关联该锁的锁记录之前,最后一个空闲的锁记录(没有指向任何锁对象)。
请求锁的源码如下:

CASE(_monitorenter): {
  // lockee 就是锁对象
  oop lockee = STACK_OBJECT(-1);
  // derefing's lockee ought to provoke implicit null check
  CHECK_NULL(lockee);
  // 如果 lock record 列表里,已存在关联该锁的 lock record
  // 则找在其之前的最后一个空闲 lock record
  // 否则找最后一个空闲 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;
    // 碰到已存在关联该锁的 lock record,直接退出
    else if (most_recent->obj() == lockee) break;
    most_recent++;
  }
  // entry 不为 NULL,代表还有空闲的 lock record
  if (entry != NULL) {
  ...
  } else {
    // lock record 不够,重新执行
    istate->set_msg(more_monitors);
    UPDATE_PC_AND_RETURN(0); // Re-execute
  }
}

而撤销偏向锁时,遍历偏向所有者的锁记录,也是按照从低地址到高地址的顺序,但它没有 break 的逻辑,因为它要处理所有关联该锁的锁记录。所以退出循环后,highest_lock 指向的是最早关联该锁的锁记录。

二、批量撤销

这篇:源码解析-触发批量撤销或批量重偏向的条件,介绍了批量撤销的触发条件。

包含批量撤销逻辑的源码:

// 本来只要撤销 o 这一个对象的偏向锁
// 但是在这次单个对象的偏向撤销中,触发了批量重偏向或批量撤销
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) {
    // 批量重偏向的逻辑
    ...
  } else {
    ...
    // 批量撤销的逻辑
    // 首先,禁用 类元数据 里的可偏向属性
    // markOopDesc::prototype() 返回的是一个关闭偏向模式的 prototype
    klass->set_prototype_header(markOopDesc::prototype());

    // 其次,遍历所有线程的栈,撤销该类正在被持有的偏向锁为轻量级锁
    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() 方法做的
          revoke_bias(owner, false, true, requesting_thread);
        }
      }
    }

    // 当前锁对象可能未被任何线程持有
    // 所以这里单独进行撤销,以确保完成调用方的撤销语义
    revoke_bias(o, false, true, requesting_thread);
  }

  ...

  }

禁用类的可偏向属性有两点作用:

  1. 该类新分配的对象都是 无锁 状态。
  2. 后续,线程请求该类的偏向锁对象时,发现类的可偏向属性被禁用,则直接加轻量级锁。(针对的是批量撤销时,未被线程正在持有的偏向锁。)

对于批量撤销时,正在被线程持有的偏向锁,通过在安全点遍历所有 Java 线程的栈,将偏向锁撤销为轻量级锁。

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