JVM的關閉鉤子

什麼是關閉鉤子(Shutdown Hook)?先看看JavaDoc的說明:java

關閉鉤子是指經過Runtime.addShutdownHook註冊的但還沒有開始的線程。這些鉤子能夠用於實現服務或者應用程序的清理工做,例如刪除臨時文件,或者清除沒法由操做系統自動清除的資源。數據庫

JVM既能夠正常關閉,也能夠強行關閉。正常關閉的觸發方式有多種,包括:當最後一個「正常(非守護)」線程結束時,或者當調用了System.exit時,或者經過其餘特定於平臺的方法關閉時(例如發送了SIGINT信號或者鍵入Ctrl-C)。安全

在正常關閉中,JVM首先調用全部已註冊的關閉鉤子JVM並不能保證關閉鉤子的調用順序。在關閉應用程序線程時,若是有(守護或者非守護)線程仍然在執行,那麼這些線程接下來將與關閉進程併發執行。當全部的關閉鉤子都執行結束時,若是runFinalizersOnExit爲true【經過Runtime.runFinalizersOnExit(true)設置】,那麼JVM將運行這些Finalizer(對象重寫的finalize方法),而後再中止。JVM不會中止或中斷任何在關閉時仍然運行的應用程序線程。當JVM最終結束時,這些線程將被強行結束。若是關閉鉤子或者Finalizer沒有執行完成,那麼正常關閉進程「掛起」而且JVM必須被強行關閉。當JVM被強行關閉時,只是關閉JVM,並不會運行關閉鉤子併發

在編寫關閉鉤子時,須要注意如下幾點:函數

  • 關閉鉤子應該是線程安全的:它們在訪問共享數據時,必須使用同步機制,而且當心地避免發生死鎖,這與其餘併發代碼的要求相同。
  • 關閉鉤子不該該對應用程序的狀態(例如,其餘服務是否已經關閉,或者全部的正常線程是否已經執行完成)或者JVM的關閉緣由作出任何假設,所以在編寫關閉鉤子的代碼時,必須考慮周全。
  • 關閉鉤子必須儘快退出,由於 他們會延遲JVM的結束時間,而用戶可能但願JVM儘快 終止。

下面是一個簡單的示例:this

public class T {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) throws Exception {
		//啓用退出JVM時執行Finalizer
		Runtime.runFinalizersOnExit(true);
		MyHook hook1 = new MyHook("Hook1");
		MyHook hook2 = new MyHook("Hook2");
		MyHook hook3 = new MyHook("Hook3");
		
		//註冊關閉鉤子
		Runtime.getRuntime().addShutdownHook(hook1);
		Runtime.getRuntime().addShutdownHook(hook2);
		Runtime.getRuntime().addShutdownHook(hook3);
		
		//移除關閉鉤子
		Runtime.getRuntime().removeShutdownHook(hook3);
		
		//Main線程將在執行這句以後退出
		System.out.println("Main Thread Ends.");
	}
}

class MyHook extends Thread {
	private String name;
	public MyHook (String name) {
		this.name = name;
		setName(name);
	}
	public void run() {
		System.out.println(name + " Ends.");
	}
	//重寫Finalizer,將在關閉鉤子後調用
	protected void finalize() throws Throwable {
		System.out.println(name + " Finalize.");
	}
}

和(可能的)執行結果(由於JVM不保證關閉鉤子的調用順序,所以結果中的第2、三行可能出現相反的順序):spa

Main Thread Ends.
Hook2 Ends.
Hook1 Ends.
Hook3 Finalize.
Hook2 Finalize.
Hook1 Finalize.

能夠看到,main函數執行完成,首先輸出的是Main Thread Ends,接下來執行關閉鉤子,輸出Hook2 Ends和Hook1 Ends。這兩行也能夠證明:JVM確實不是以註冊的順序來調用關閉鉤子的。而因爲hook3在調用了addShutdownHook後,接着對其調用了removeShutdownHook將其移除,因而hook3在JVM退出時沒有執行,所以沒有輸出Hook3 Ends。操作系統

另外,因爲MyHook類實現了finalize方法,而main函數中第一行又經過Runtime.runFinalizersOnExit(true)打開了退出JVM時執行Finalizer的開關,因而3個hook對象的finalize方法被調用,輸出了3行Finalize。線程

注意,屢次調用addShutdownHook來註冊同一個關閉鉤子將會拋出IllegalArgumentException:日誌

Exception in thread "main" java.lang.IllegalArgumentException: Hook previously registered
	at java.lang.ApplicationShutdownHooks.add(ApplicationShutdownHooks.java:72)
	at java.lang.Runtime.addShutdownHook(Runtime.java:211)
	at T.main(T.java:12)

另外,從JavaDoc中得知:

Once the shutdown sequence has begun it can be stopped only by invoking the halt method, which forcibly terminates the virtual machine.

Once the shutdown sequence has begun it is impossible to register a new shutdown hook or de-register a previously-registered hook. Attempting either of these operations will cause an IllegalStateException to be thrown.

「一旦JVM關閉流程開始,就只能經過調用halt方法來中止該流程,也不可能再註冊或移除關閉鉤子了,這些操做將致使拋出IllegalStateException」。

若是在關閉鉤子中關閉應用程序的公共的組件,如日誌服務,或者數據庫鏈接等,像下面這樣:

Runtime.getRuntime().addShutdownHook(new Thread() {
	public void run() {
		try { 
			LogService.this.stop();
		} catch (InterruptedException ignored){
			//ignored
		}
	}
});

因爲關閉鉤子將併發執行,所以在關閉日誌時可能致使其餘須要日誌服務的關閉鉤子產生問題。爲了不這種狀況,可使關閉鉤子不依賴那些可能被應用程序或其餘關閉鉤子關閉的服務。實現這種功能的一種方式是對全部服務使用同一個關閉鉤子(而不是每一個服務使用一個不一樣的關閉鉤子),而且在該關閉鉤子中執行一系列的關閉操做。這確保了關閉操做在單個線程中串行執行,從而避免了在關閉操做以前出現競態條件或死鎖等問題。

相關文章
相關標籤/搜索