Реализация нижнего уровня Deadly Synchronized — предвзятая блокировка

Java задняя часть JVM C++

Эта статья является второй в синхронизированной серии. Основное содержание — анализ реализации предвзятой блокировки.

Предыстория рождения и основные принципы замков со смещением были упомянуты выше.Настоятельно рекомендуется прочитать предыдущую статьюстатьяПрочитайте эту статью на основе.

Больше статей смотрите в личном блоге:GitHub.com/farmer Джон Брат…

В этой серии статей мы рассмотрим HotSpotsynchronizedВсесторонний анализ реализации блокировок, включая анализ принципа и исходного кода предвзятых блокировок, упрощенных блокировок и блокировок тяжелого веса, блокировок, разблокировок и процессов обновления блокировок.synchronizedНекоторая помощь от однокурсников на дороге. В основном включают следующие статьи:

Реализация Deadly Synchronized Bottom — Введение

Реализация нижнего уровня Deadly Synchronized — предвзятая блокировка

Базовая реализация Deadly Synchronized — облегченная блокировка

Реализация нижнего уровня Deadly Synchronized — усиленная блокировка

Эта статья будет разделена на несколько частей:

1. Смещенный ко входу замок

2. Процесс получения блокировки смещения

3. Процесс отзыва предвзятых блокировок

4. Процесс снятия блокировки смещения

5. Пакетное повторное смещение и пакетный отзыв смещенных блокировок

Версия JVM, проанализированная в этой статье, — JVM 8. Конкретный номер версии и код можно найти вздесьВидеть.

Вход с косым замком

В настоящее время во многих статьях в Интернете было найдено неправильное место для записи исходного кода блокировки смещения, из-за чего я не смог понять большую часть логики блокировки смещения раньше и потребовал много обходных путей.

synchronizedразделен наsynchronizedкодовые блоки иsynchronizedметод, основная логика получения замков такая же, в этой статье объясняетсяsynchronizedРеализация кодовых блоков. Как упоминалось в предыдущей статье,synchronizedБлок кода сделанmonitorenterиmonitorexitреализуется двумя инструкциями.

Что касается записи для получения блокировок на виртуальной машине HotSpot, во многих статьях в Интернете указана запись метода какinterpreterRuntime.cpp#monitorenter, или данная записьbytecodeInterpreter.cpp#1816. В том числе этот от Zhan XiaolangстатьяТак же есть проблема с положением входа в замок (конечно статья еще очень хорошая, когда я только начал исследоватьsynchronizedВ то время мне очень помогла эта статья брата Сяолана).

Чтобы найти вход в замок, вы должны найти правильный код в исходном коде.monitorenterГде разбираются директивы. В HotSpot есть два местаmonitorenterИнструкции разбираются: одна находится вbytecodeInterpreter.cpp#1816, другой вtemplateTable_x86_64.cpp#3667.

Первый является интерпретатором байт-кода в JVM (bytecodeInterpreter), каждая инструкция JVM реализована на C++ (например,monitorenter,invokevirtualи т. д.), преимуществом которого является относительно простая реализация и легкость понимания, а недостатком — медленное выполнение. Последний является интерпретатором шаблонов (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
  }
}

Рассмотрим формат слова-метки в заголовке объекта:

image

Каждый класс в JVM также имеет прототип_заголовок, похожий на слово метки, которое используется для маркировки такой информации, как эпоха и переключатель смещения класса. в коде вышеlockee->klass()->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)Или текущий идентификатор потока и прототип_заголовка класса, полученное значение (текущий идентификатор потока + (эпоха + возраст поколения + смещенный флаг блокировки + флаг блокировки) в текущем идентификаторе потока + прототип_заголовок), обратите внимание на возраст поколения прототип_заголовок 4 байта равны 0

Вторая часть^ (uintptr_t)markXOR результата, вычисленного выше, с помощью markOop объекта блокировки, все равные биты устанавливаются в 0, и остаются только неравные биты.

третья часть& ~((uintptr_t) markOopDesc::age_mask_in_place)markOopDesc::age_mask_in_place равен... 0001111000. После отрицания становится... 1110000111. За исключением 4 бит возраста поколения, все остальные биты равны 1. Инвертированный результат добавляется к приведенному выше результату, игнорируя поколение age в результате, полученном выше XOR.

code 5,anticipated_bias_locking_value==0Поток, представляющий смещение, является текущим потоком иmark wordЭпоха равна эпохе класса, в этом случае ничего делать не нужно.

code 6,(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0Prototype_header, представляющий класс или объектmark wordРежим среднего смещения отключен, а поскольку можно идти, это прошлоmark->has_bias_pattern()суждение, объектmark wordВключен режим среднего смещения, что означает, что прототип_заголовка класса не имеет смещения.

Затем используйте команду CASAtomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == markДля снятия предвзятой блокировки мы знаем, что у CAS будет несколько параметров, 1 — ожидаемое исходное значение, 2 — ожидаемое измененное значение, 3 — объект, который будет изменен, соответствующий ему, первый параметр метода cmpxchg_ptr — это ожидаемое измененное значение значения, второй параметр — измененный объект, третий параметр — ожидаемое исходное значение, метод возвращает фактическое исходное значение, если оно равно ожидаемому исходному значению, модификация прошла успешно.

код 7, если эпоха истекла, вам нужно повторно сместить, используйте инструкцию CAS, чтобы заблокировать объектmark wordЗаменена новой эпохой, смещенной в сторону текущей темы и эпохи эпохи класса.mark word.

В коде 8 CAS меняет смещенный поток на текущий.Если текущий уклон анонимен, его можно успешно модифицировать, иначе он войдет в логику эскалации блокировки.

код 9, этот шаг уже является логикой облегченного замка. сверхуmark wordФормат можно увидеть в облегченном замкеmark wordхранится указывает наLock Recordуказатель. Это создает состояние без блокировкиmark word, затем сохраните вLock Record(Lock RecordФормат можно увидеть в первой статье). настраиватьmark wordПричина отсутствия блокировки: когда облегченная блокировка разблокирована, заголовок объектаmark wordУстановить какLock RecordсерединаDisplaced Mark Word, поэтому при создании он устанавливается в состояние без блокировки, и его можно заменить непосредственно с помощью CAS, когда он разблокирован.

код 10, если это повторный вход в замок, это будетLock RecordизDisplaced Mark WordУстановите значение null, которое действует как счетчик повторного входа в блокировку.

Выше приведен процесс блокировки смещенных блокировок (включая процесс блокировки некоторых облегченных блокировок): если текущая блокировка смещена в сторону других потоков || значение эпохи истекает || режим смещения закрыт || В процессе возникает конфликт параллелизма приобретения предвзятых замков, войдет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метод, если это поток виртуальной машины, он перейдет к следующемуBiasedLocking::revoke_at_safepoint. Мы в основном смотрим наBiasedLocking::revoke_and_rebiasметод. Основная функция этого метода аналогична его названию: revoke или re-bias, первый параметр инкапсулирует объект блокировки и текущий поток, а второй параметр указывает, разрешать ли re-bias, что здесь верно.

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 1,code 2Поток B не пойдет и в конечном итоге пойдетcode 4В этот момент, если отменяемая блокировка смещена в сторону текущего потока, она будет вызываться напрямую.revoke_biasОтмените блокировку смещения, иначе операция будет передана потоку виртуальной машины до тех пор, покаsafepointвремя выполнять.

О потоке VM здесь: в JVM есть специальный поток VM, который будет постоянно получать запросы из VMOperationQueue, такие как запросы GC. по необходимостиsafepointОперация (VM_Operationevaluate_at_safepoint возвращает true) должна ждать, пока все потоки Java не войдут вsafepointтолько начал выполнять. оsafepointВы можете обратиться к этомустатья.

Далее сосредоточимся на анализеrevoke_biasметод. Первый параметр — это объект блокировки, а второй и третий параметры — 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#hashилиSystem.identityHashCode()метод, вызывающий предвзятую блокировку или упрощенную эскалацию блокировки для этого объекта. Это связано с тем, что хэш-код объекта в Java генерируется при вызове этих двух методов, и если он находится в состоянии без блокировки, он сохраняется вmark word, если это тяжеловесная блокировка, то она хранится в соответствующем мониторе, а смещенной блокировке негде хранить информацию, поэтому ее необходимо обновить. Подробности см. в этомстатьяизhashcode()方法对偏向锁的影响раздел (примечание: в описании предвзятой блокировки в этой статье есть некоторые ошибки), а также я задал автору этой статьи несколько вопросов, и он мне с энтузиазмом ответил,Спасибо!

Ближе к дому,revoke_biasЛогика метода:

  1. Проверьте, жив ли смещенный поток, и, если он больше не жив, отзовите смещенную блокировку напрямую. JVM поддерживает коллекцию для хранения всех выживших потоков и оценивает, является ли поток живым, проходя по коллекции.
  2. Находится ли смещенный поток в синхронизированном блоке, если нет, отмените смещенную блокировку. Давайте рассмотрим процесс блокировки предвзятых блокировок: каждый раз, когда вы вводите синхронизированный блок (т. е. выполняетеmonitorenter) найдет первый доступный в стеке в порядке от старшего к младшемуLock Record, указывая свое поле obj на объект блокировки. Каждый раз, когда вы разблокируете (т.е. выполняетеmonitorexit) будет связан с самым низкимLock Recordудаленный. Таким образом, вы можете перемещаться по стеку потоков с помощьюLock Recordчтобы определить, находится ли поток все еще в синхронизированном блоке.
  3. будет предвзято относиться к темам, связанным со всемиLock RecordизDisplaced Mark Wordустановить в ноль, тогда самый значимыйLock RecordизDisplaced Mark WordУстановите в свободное от блокировки состояние, наиболее важноеLock RecordТо есть, когда блокировка приобретается в первый разLock Record(Первый раз здесь относится к первому разу, когда повторный вход получает блокировку), а затем указываете заголовок объекта на самый старший битLock Record, здесь нет необходимости использовать инструкцию CAS, потому что она находится вsafepoint. После исполнения он модернизируется до облегченного замка. Все записи блокировки оригинальной предвзятой ветки стали облегченными замками. Если вы этого не понимаете, ознакомьтесь с процессом облегченной блокировки блокировки в предыдущей статье.

Снятие блокировки смещения

Запись о выпуске для смещенной блокировки находится по адресу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 в заголовке объекта. Если 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_REVOKEилиHR_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Если это правда, это означает пакетное изменение смещения, в противном случае это пакетная отмена.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Обработайте объект блокировки, который используется в данный момент, пройдя стеки всех активных потоков, найдите все используемые объекты блокировки со смещением, а затем обновите их значения эпохи. То есть он не будет смещен в сторону используемого замка, иначе он разрушит потокобезопасность замка.

Логика пакетной отмены выглядит следующим образом:

code 3Отключите флаг смещения класса, а затем, когда существующий экземпляр класса получит блокировку, он будет обновлен до облегченной блокировки; вновь выделенный объект класса имеетmark wordЭто режим без блокировки.

code 4Обработайте объект блокировки, который используется в данный момент, найдите все используемые объекты смещенной блокировки, просмотрев стеки всех выживших потоков, а затем отмените смещенную блокировку.