jvm進(jìn)程如何感知關(guān)閉事件
java.lang.Shutdown
結(jié)束一個普通的java進(jìn)程,一般來說可以讓程序自行結(jié)束惠赫,也可以通過System.exit(n);來主動觸發(fā)終止故黑。
如果是linux系統(tǒng)场晶,還可以通過外部信號來終止進(jìn)程。
一般來說停止一個服務(wù)常用的方式就是kill -2 pid(ctrl + C)钳宪、kill -9 pid吏颖、kill -15 pid
kill -9 可以認(rèn)為操作系統(tǒng)從內(nèi)核級別直接強(qiáng)行kill進(jìn)程恨樟,對進(jìn)程來說沒有任何的準(zhǔn)備,且無法監(jiān)聽-9信號奉呛。
kill -2 和 -15 則是操作系統(tǒng)給該進(jìn)程發(fā)送一個信號通知瞧壮,告知應(yīng)用主動關(guān)閉匙握,應(yīng)用可以監(jiān)聽并接收到信號圈纺,可以完成一些關(guān)閉回收等動作,然后自我停止灯谣。
jvm專門有個Signal Dispatcher線程來接收信號蛔琅。
在Shutdown
針對這幾類終止方式提供了兩個處理方法。
/* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon thread has finished.
Unlike the exit method, this method does not actually halt the VM. */
static void shutdown() {
synchronized (lock) {
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and then return */
case FINALIZERS:
break;
}
}
synchronized (Shutdown.class) {
sequence();
}
}
結(jié)合方法注釋钩述,當(dāng)前進(jìn)程如果所有的非守護(hù)線程執(zhí)行完成牙勘,會由JNI DestroyJavaVM觸發(fā)shutdown方法調(diào)用所禀。此方法并沒有halt(停止)VM北秽。
/* Invoked by Runtime.exit, which does all the security checks.
* Also invoked by handlers for system-provided termination events, which should pass a nonzero status code.
*/
static void exit(int status) {
boolean runMoreFinalizers = false;
synchronized (lock) {
if (status != 0) runFinalizersOnExit = false;
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and halt */
break;
case FINALIZERS:
if (status != 0) {
/* Halt immediately on nonzero status */
halt(status);
} else {
// Compatibility with old behavior: Run more finalizers and then halt
runMoreFinalizers = runFinalizersOnExit;
}
break;
}
}
if (runMoreFinalizers) {
runAllFinalizers();
halt(status);
}
synchronized (Shutdown.class) {
//Synchronize on the class object, causing any other thread that attempts to initiate shutdown to stall indefinitely
sequence();
halt(status);
}
}
當(dāng)在代碼中調(diào)用了Runtime.exit(System.exit)贺氓,會調(diào)用此exit方法辙培,status
是0表示正常退出邢锯,非0表示異常退出,此方法會主動halt VM尾抑。
除了進(jìn)程退出的處理方法外再愈,在ShutDown類中护戳,還定義了hook,允許我們在進(jìn)程停止前抗悍,完成一些清場的操作钳枕。
// The system shutdown hooks are registered with a predefined slot.
// The list of shutdown hooks is as follows:
// (0) Console restore hook
// (1) Application hooks
// (2) DeleteOnExit hook
private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
每個hook對應(yīng)一個線程鱼炒,會在add
方法中添加,在runHooks
中依次執(zhí)行俐巴。
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null)
throw new InternalError("Shutdown hook at slot " + slot + " already registered");
// ... 異常場景判斷
hooks[slot] = hook;
}
}
/* Run all registered shutdown hooks */
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
// acquire the lock to make sure the hook registered during shutdown is visible here.
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
如何添加自定義的shutdown hook
Shutdown
類屬于系統(tǒng)的操作類擎鸠,并沒有暴露給應(yīng)用層使用缘圈。如果我們想定義自己的shutdown hook糟把,可以使用Runtime.getRuntime().addShutdownHook()
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
這個方法調(diào)用了ApplicationShutdownHooks.add(hook)
ApplicationShutdownHooks
class ApplicationShutdownHooks {
private static IdentityHashMap<Thread, Thread> hooks;
static {
try {
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
// application shutdown hooks cannot be added if
// shutdown is in progress.
hooks = null;
}
}
/* Add a new shutdown hook. Checks the shutdown state and the hook itself, but does not do any security checks. */
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
// ...
}
ApplicationShutdownHooks
在類初始化過程完成了hook回調(diào)的注冊雄可,并初始化了IdentityHashMap,當(dāng)有自定義的hook被添加時(shí)数苫,緩存到map中虐急。
當(dāng)shutdown被觸發(fā)后滔迈,會通過hook回調(diào)來調(diào)用到runHooks()
/* Iterates over all application hooks creating a new thread for each
* to run in. Hooks are run concurrently and this method waits for
* them to finish.
*/
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
runHooks()
會遍歷啟動的每一個hook線程,并通過join
來等待所有hook執(zhí)行完成敬惦。因?yàn)檫@個hook線程是并行操作的仁热,所以這里無法保證hook的執(zhí)行順序勾哩。
總結(jié)
- jvm進(jìn)程的關(guān)閉會由JNI觸發(fā)
Shutdown
類中的exit()
或shutdown()
, 這兩個方法會調(diào)用hook回調(diào)。 - 自定義的shutdown hook通過
Runtime.getRuntime().addShutdownHook()
添加到進(jìn)程中思劳。 - 多個自定義shutdown hook并行執(zhí)行潜叛,不保證執(zhí)行順序壶硅。