1. 程式人生 > 實用技巧 >「HotSpotVM」執行緒執行狀態切換

「HotSpotVM」執行緒執行狀態切換

執行緒執行狀態切換(VM<->Java<->Native)

1.ThreadInVMfromJava

1.1 Java->VM

假如直譯器一些程式碼比較複雜,或者因為其他原因,需要C++的支援,那麼他會jmp到interpreterRuntime。比如anewarray建立Object[],這個位元組碼就會從jit code跳到InterpreterRuntime::anewarray。這個函式有一個特別的地方是它用JRT_ENTRY和JRT_END包裹著,這個巨集展開是一個ThreadInVMfromJava結構。

JRT_ENTRY(void, InterpreterRuntime::anewarray(JavaThread* thread, ConstantPool* pool, int index, jint size))
  ...
JRT_END

#define JRT_ENTRY(result_type, header)                               \
  result_type header {                                               \
    ThreadInVMfromJava __tiv(thread);                                \
    VM_ENTRY_BASE(result_type, header, thread)                       \
    debug_only(VMEntryWrapper __vew;)

ThreadInVMfromJava是指執行緒從Java程式碼部分走到了VM程式碼部分,虛擬機器精確的知道當前執行緒在執行什麼程式碼:

class ThreadInVMfromJava : public ThreadStateTransition {
 public:
  ThreadInVMfromJava(JavaThread* thread) : ThreadStateTransition(thread) {
    // 下面其實就是thread->set_thread_state(_thread_in_vm)
    // 給執行緒設定一個狀態_thread_in_vm
    trans_from_java(_thread_in_vm);
  }
  ~ThreadInVMfromJava()  {
    if (_thread->stack_overflow_state()->stack_yellow_reserved_zone_disabled()) {
      _thread->stack_overflow_state()->enable_stack_yellow_reserved_zone();
    }
    trans(_thread_in_vm, _thread_in_Java);
    // Check for pending. async. exceptions or suspends.
    if (_thread->has_special_runtime_exit_condition()) _thread->handle_special_runtime_exit_condition();
  }
};

ThreadInVMfromJava的建構函式——相當於進入從Java程式碼進入VM程式碼的InterpreterRuntime::anewarray——只是簡單的給執行緒設定一個狀態,但是它的解構函式——相當於從VM程式碼的InterpreterRuntime::anewarray進入Java程式碼——卻稍微複雜一些。

1.2 Java->VM->Java

解構函式裡面有一個trans call:

static inline void transition(JavaThread *thread, JavaThreadState from, JavaThreadState to) {
    ... // some asserts
    thread->set_thread_state((JavaThreadState)(from + 1));

    InterfaceSupport::serialize_thread_state(thread);

    SafepointMechanism::block_if_requested(thread);
    thread->set_thread_state(to);

    CHECK_UNHANDLED_OOPS_ONLY(thread->clear_unhandled_oops();)
  }

這個trans大概做了三件事情:

  1. 將執行緒狀態設定為_thread_in_vm_trans ,表示執行緒正處於vm轉移到其他狀態這個過程中
  2. 檢查安全點
  3. 將執行緒狀態設定為_thread_in_Java,表示執行緒進入Java程式碼。


最重要的是處理safepoint:

void SafepointSynchronize::block(JavaThread *thread) {
  ...
  JavaThreadState state = thread->thread_state();
  switch(state) {
    case _thread_in_vm_trans:
    case _thread_in_Java:        // From compiled code
      // We are highly likely to block on the Safepoint_lock. In order to avoid blocking in this case,
      // we pretend we are still in the VM.
      thread->set_thread_state(_thread_in_vm);

      // 如果正在開啟安全點中,將_waiting_to_block--
      // VMThread先拿到safepoint lock再修改——waiting_to_block,所以這裡也需要拿到鎖再改
      if (is_synchronizing()) {
         Atomic::inc (&TryingToBlock) ;
      }

      Safepoint_lock->lock_without_safepoint_check();
      if (is_synchronizing()) {
        assert(_waiting_to_block > 0, "sanity check");
        _waiting_to_block--;
        thread->safepoint_state()->set_has_called_back(true);

        if (thread->in_critical()) {
          increment_jni_active_count();
        }

        if (_waiting_to_block == 0) {
          Safepoint_lock->notify_all();
        }
      }

      // 將執行緒狀態設定為_thread_blocked
      thread->set_thread_state(_thread_blocked);
      Safepoint_lock->unlock();
      // 因為Theads_lock已經被VMThread執行緒拿了,所以當前執行緒走到這裡就會阻塞。
      Threads_lock->lock_without_safepoint_check();
      // 恢復狀態為_thread_in_vm_trans
      thread->set_thread_state(state);
      Threads_lock->unlock();
      break;

    case _thread_in_native_trans:
    case _thread_blocked_trans:
    case _thread_new_trans:
      ...
      break;

    default:
     fatal("Illegal threadstate encountered: %d", state);
  }
  ...
}

其實,安全點無非就三種情況:還沒開,正在開,已經開了。[1]

還沒開,那沒我什麼事,繼續執行就ok。

正在開,VMThread正在開的時候有一個_waiting_to_block計數,表示要等多少個其他執行緒block,只有當_waiting_to_block為0時VMThread安全點才能完全開啟。所以如果這裡遇到VMThread正在開安全點,那麼當前執行緒就將_waiting_to_block減1,告訴開安全點的執行緒:我馬上就阻塞了,你繼續執行吧,我不會影響你的。果然馬上接下來就那Thread_lock,然後會阻塞在這裡。直到安全點關閉。[]


已經開了。那更簡單,直接走到那Thread_lock那裡阻塞住,直到安全點關閉。


上面有一句話沒解釋清楚:

果然馬上接下來就那Thread_lock,然後會阻塞在這裡。直到安全點關閉。

為什麼拿Thread_lock阻塞?


因為安全點的開關對應兩個函式:SafepointSynchronize::begin()和SafepointSynchronize::end(),在begin裡面VMThread拿到Threads_lock,然後在end裡面釋放,只有VMThread可以開/關安全點,所以只要VMThread在
begin() .... end() 這個區間執行期間,任何其他嘗試拿Threads_lock的執行緒都會block。

上面這些內容,總結來說就一句話:如果正在開啟安全點或者已經開啟安全點,那麼_thread_in_vm狀態的執行緒不能切換到_thread_in_Java狀態,他會block。


2. ThreadInVMfromNative

2.1 Native->VM

class ThreadInVMfromNative : public ThreadStateTransition {
 public:
  ThreadInVMfromNative(JavaThread* thread) : ThreadStateTransition(thread) {
    trans_from_native(_thread_in_vm);
  }
  ~ThreadInVMfromNative() {
    trans_and_fence(_thread_in_vm, _thread_in_native);
  }
};

建構函式的trans_from_native最終呼叫這個:

static inline void transition_from_native(JavaThread *thread, JavaThreadState to) {
    assert((to & 1) == 0, "odd numbers are transitions states");
    assert(thread->thread_state() == _thread_in_native, "coming from wrong thread state");
    // Change to transition state
    thread->set_thread_state(_thread_in_native_trans);

    InterfaceSupport::serialize_thread_state_with_handler(thread);

    // We never install asynchronous exceptions when coming (back) in
    // to the runtime from native code because the runtime is not set
    // up to handle exceptions floating around at arbitrary points.
    if (SafepointMechanism::poll(thread) || thread->is_suspend_after_native()) {
      JavaThread::check_safepoint_and_suspend_for_native_trans(thread);

      // Clear unhandled oops anywhere where we could block, even if we don't.
      CHECK_UNHANDLED_OOPS_ONLY(thread->clear_unhandled_oops();)
    }

    thread->set_thread_state(to);
  }

從_thread_in_native到_thread_in_vm要比從_thread_in_Java到_thread_in_vm麻煩一些,後者簡單地將狀態設定為_thread_in_vm就表示進入了,前者大體上有三步:(和_thread_in_vm到_thread_in_Java一樣)

  1. 設定狀態為_thread_in_native_trans,表示正在從native狀態過渡到其他狀態
  2. 檢查安全點
  3. 設定狀態為_thread_in_vm,表示成功從native進入vm狀態

檢查狀態點和之前有一點區別,只有當安全點已經開啟後,才會呼叫SafepointSynchronize::block阻塞當前執行緒,如果是正在開,或者關閉的,那麼是可以從native轉移到vm狀態而不需要阻塞的。


既然不會檢查是否正在開啟安全點,那麼SafepointSynchronize::block與之前也有一點小不同:

void SafepointSynchronize::block(JavaThread *thread) {
  ...
  switch(state) {
    case _thread_in_vm_trans:
    case _thread_in_Java:        // From compiled code
      ...// 前面已經提到
    case _thread_in_native_trans:
    case _thread_blocked_trans:
    case _thread_new_trans:
      if (thread->safepoint_state()->type() == ThreadSafepointState::_call_back) {
        thread->print_thread_state();
        fatal("Deadlock in safepoint code.  "
              "Should have called back to the VM before blocking.");
      }
      // 設定狀態為_thread_blocked
      thread->set_thread_state(_thread_blocked);
      
      Threads_lock->lock_without_safepoint_check();
      thread->set_thread_state(state);
      Threads_lock->unlock();
      
      break;
  }
}

沒有拿safepoint lock、檢查is_ synchronizing()的邏輯,直接阻塞完事。

2.2 Native->VM->Native

從_thread_in_vm到_thread_in_native的邏輯和從_thread_in_vm到_thread_in_Java幾乎一模一樣,這裡就不貼了。

3. ThreadToNativeFromVM

除了2之外還有一個VM->Native->VM,他相當與將2的構造和析構換了個位置,程式碼幾乎一樣,可以對照著看:

class ThreadInVMfromNative : public ThreadStateTransition {
 public:
  ThreadInVMfromNative(JavaThread* thread) : ThreadStateTransition(thread) {
    trans_from_native(_thread_in_vm); //1
  }
  ~ThreadInVMfromNative() {
    trans_and_fence(_thread_in_vm, _thread_in_native); //2
  }
};


class ThreadToNativeFromVM : public ThreadStateTransition {
 public:
  ThreadToNativeFromVM(JavaThread *thread) : ThreadStateTransition(thread) {
    assert(!thread->owns_locks(), "must release all locks when leaving VM");
    thread->frame_anchor()->make_walkable(thread);
    trans_and_fence(_thread_in_vm, _thread_in_native); //2
    if (_thread->has_special_runtime_exit_condition()) _thread->handle_special_runtime_exit_condition(false);
  }

  ~ThreadToNativeFromVM() {
    trans_from_native(_thread_in_vm); //1
    assert(!_thread->is_pending_jni_exception_check(), "Pending JNI Exception Check");
    // We don't need to clear_walkable because it will happen automagically when we return to java
  }
};

4. ThreadToJavaFromVM

哈,其實是沒有這個ThreadToJavaFromVM結構的。但是可以確定,vm->java->vm其中vm到java肯定需要安全點檢查的。


vm到java是由JavaCalls::call_helper完成的,它的邏輯如下:

void JavaCalls::call_helper(...) {
    ...
    // do call
  { JavaCallWrapper link(method, receiver, result, CHECK);
    { HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner

      // NOTE: if we move the computation of the result_val_address inside
      // the call to call_stub, the optimizer produces wrong code.
      intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());
      intptr_t* parameter_address = args->parameters();
      StubRoutines::call_stub()(
        (address)&link,
        // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
        result_val_address,          // see NOTE above (compiler problem)
        result_type,
        method(),
        entry_point,
        parameter_address,
        args->size_of_parameters(),
        CHECK
      );

      result = link.result();  // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
      // Preserve oop return value across possible gc points
      if (oop_result_flag) {
        thread->set_vm_result((oop) result->get_jobject());
      }
    }
  } // Exit JavaCallWrapper (can block - potential return oop must be preserved)
}

call java的真正邏輯在call_stub裡面(實際上還隔了比較遠),在call_stub外面有個JavaCallWrapper,這個包裝物件會負責狀態的轉換,同時也包括安全點檢查:

JavaCallWrapper::JavaCallWrapper(const methodHandle& callee_method, Handle receiver, JavaValue* result, TRAPS) {
  ...
  ThreadStateTransition::transition(thread, _thread_in_vm, _thread_in_Java);
  ...
}
JavaCallWrapper::~JavaCallWrapper() {
  ...
  ThreadStateTransition::transition_from_java(_thread, _thread_in_vm);
  ...
}

所以,JavaCallWrapper才是上面三種結構的等價物。

5. 總結


TL;DR. 總結一下上面的狀態轉換:

Wrapper Trans Desc
(ThreadInVMfromJava)Java->VM->Java Java->VM:一定可以轉換,只需要改變執行緒狀態(0) VM->Java:如果安全點正在開,或者已經開了,那麼不能轉換,執行緒阻塞。(1)
(ThreadInVMfromNative)Native->VM->Native Native->VM:如果安全點已經開了,那麼不能轉換,執行緒阻塞。(2) VM -> Native:與(1)一致
(ThreadToNativeFromVM)VM->Native->VM VM->Native:與(1)一致 Native->VM:與(2)一致
(JavaCallWrapper)VM->Java->VM VM->Java:與(1)一致 Java->VM:與(0)一致

note:這裡說的一致實際上是“幾乎一致”,嚴格來說還是有一些區別的,只是不影響主要流程。


它們相當於將JVM執行的程式碼劃分成了三部分:Java程式碼、VM程式碼、native程式碼,對於安全點是有重要意義的。比如說,當VMThread請求開啟安全點的時候,他要求java執行緒停止執行,那麼java執行緒怎麼停止呢?一個最簡單的方式就是發生Java->VM->Java的轉換,比如上面java程式碼執行anewarray位元組碼的時候,就會在析構裡面檢查是否開啟安全點,然後停止。

footnote

[1] 狹義的說,安全點的開、關只是將一片記憶體的讀寫許可權進行修改,所以不會存在開、正在開、關這種狀態劃分,這裡的安全點開、關其實是對應SafepointSynchronize::begin()和end()兩個函式。