「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大概做了三件事情:
- 將執行緒狀態設定為_thread_in_vm_trans ,表示執行緒正處於vm轉移到其他狀態這個過程中
- 檢查安全點
- 將執行緒狀態設定為_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一樣)
- 設定狀態為_thread_in_native_trans,表示正在從native狀態過渡到其他狀態
- 檢查安全點
- 設定狀態為_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()兩個函式。