前言
在編寫的Java程式中有時會遇到用 System.exit 來關閉JVM,其中呼叫 exit 方法時會包含一個狀態引數n,即System.exit(n)
。這其實是一個約定值,如果為0則表示正常關閉,而非0則表示非正常關閉。這裡我們從JDK原始碼看下不同狀態都是怎麼處理的。
System與Runtime
先看System類的exit方法如下,可以看到它是間接呼叫了Runtime物件的exit方法。
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}複製程式碼
而Runtime的exit方法如下,先使用SecurityManager檢查是否有關閉JVM的許可權,允許執行則呼叫Shutdown的exit方法。
public void exit(int status) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkExit(status);
}
Shutdown.exit(status);
}複製程式碼
Shutdown
進入到Shutdown類的exit方法,Java層面還有自己的狀態state,它可能值為RUNNING、HOOKS和FINALIZERS,可以看到裡面的主要邏輯是:
- 不管什麼狀態下,status為非0時不執行任何Finalizer。
- 在RUNNING狀態下,狀態轉成HOOKS,然後先執行sequence方法,再執行halt方法停止JVM。
- 在FINALIZERS狀態下,status為非0時直接 就呼叫halt方法停止JVM了,而status為0時還需要執行所有的finalizer,之後才呼叫halt方法停止JVM。
static void exit(int status) { boolean runMoreFinalizers = false; synchronized (lock) { if (status != 0) runFinalizersOnExit = false; switch (state) { case RUNNING: state = HOOKS; break; case HOOKS: break; case FINALIZERS: if (status != 0) { halt(status); } else { runMoreFinalizers = runFinalizersOnExit; } break; } } if (runMoreFinalizers) { runAllFinalizers(); halt(status); } synchronized (Shutdown.class) { sequence(); halt(status); } }複製程式碼
sequence方法主要是控制鉤子和Finalizer執行的順序,判斷狀態不為HOOKS則直接返回,然後執行所有的鉤子,把state改為FINALIZERS,最後執行所有finalizer。
private static void sequence() { synchronized (lock) { if (state != HOOKS) return; } runHooks(); boolean rfoe; synchronized (lock) { state = FINALIZERS; rfoe = runFinalizersOnExit; } if (rfoe) runAllFinalizers(); }複製程式碼
halt方法
執行JVM是通過halt方法實現,這時System.exit(n)
的狀態n繼續往下傳遞,最終是呼叫了一個本地的halt0方法。
static void halt(int status) {
synchronized (haltLock) {
halt0(status);
}
}
static native void halt0(int status);複製程式碼
對應的本地方法如下,主要是呼叫了JVM_Halt函式,
JNIEXPORT void JNICALL
Java_java_lang_Shutdown_halt0(JNIEnv *env, jclass ignored, jint code)
{
JVM_Halt(code);
}複製程式碼
繼續往下,JVM_Halt函式主要包含了before_exit函式和vm_exit函式,before_exit函式主要做退出前的一些工作,它只會被執行一次,在多個執行緒情況下只有獲取鎖的才能執行,其他執行緒都必須等。
JVM_ENTRY_NO_ENV(void, JVM_Halt(jint code))
before_exit(thread);
vm_exit(code);
JVM_END複製程式碼
而vm_exit函式如下,這裡code仍然是Java呼叫System.exit(n)
時傳遞來的,最主要的是vm_direct_exit函式,它先向jvm發出關閉通知,然後再呼叫exit函式退出,狀態值繼續往下傳,這時的狀態值已經傳遞到作業系統的API。
void vm_exit(int code) {
Thread* thread = ThreadLocalStorage::is_initialized() ?
ThreadLocalStorage::get_thread_slow() : NULL;
if (thread == NULL) {
vm_direct_exit(code);
}
if (VMThread::vm_thread() != NULL) {
VM_Exit op(code);
if (thread->is_Java_thread())
((JavaThread*)thread)->set_thread_state(_thread_in_vm);
VMThread::execute(&op); VM Thread.
vm_direct_exit(code);
} else {
vm_direct_exit(code);
}
ShouldNotReachHere();
}複製程式碼
void vm_direct_exit(int code) {
notify_vm_shutdown();
os::wait_for_keypress_at_exit();
::exit(code);
}複製程式碼
總結
Java的System.exit(n)的狀態碼最終是傳遞到作業系統的API,所以它的含義與作業系統API的含義相關,當然這個過程Java還會有自己的一些機制工作需要處理。可以說目前大多數平臺都可以在 main 函式中直接 return退出程式,但某些平臺下不能這樣處理,所以為了相容需要使用 exit() 來退出。
以下是廣告和相關閱讀
========廣告時間========
鄙人的新書《Tomcat核心設計剖析》已經在京東銷售了,有需要的朋友可以到 item.jd.com/12185360.ht… 進行預定。感謝各位朋友。
=========================
相關閱讀:
從JDK原始碼角度看Object
從JDK原始碼角度看Long
從JDK原始碼角度看Float
從JDK原始碼角度看Integer
volatile足以保證資料同步嗎
談談Java基礎資料型別
從JDK原始碼角度看併發鎖的優化
從JDK原始碼角度看執行緒的阻塞和喚醒
從JDK原始碼角度看併發競爭的超時
從JDK原始碼角度看java併發執行緒的中斷
從JDK原始碼角度看Java併發的公平性
從JDK原始碼角度看java併發的原子性如何保證
從JDK原始碼角度看Byte
從JDK原始碼角度看Boolean
從JDK原始碼角度看Short
關注打賞: