线程执行状态切换(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()两个函数。