JVM Shutdown Hook
須彌零一JVM Shutdown Hook
今天偶然看到?java.lang.Runtime?類的一個(gè)方法?public void addShutdownHook(Thread hook)?。
它的?javadoc?是這么寫的:
Registers a new virtual-machine shutdown hook.
The Java virtual machine shuts down in response to two kinds of events:
??The program exits normally, when the last non-daemon thread exits or when the exit (equivalently, System.exit) method is invoked, or
??The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown.
意思就是說(shuō)可以給?JVM?注冊(cè)一個(gè)鉤子,這個(gè)鉤子將在虛擬機(jī)關(guān)閉的執(zhí)行。當(dāng)然這個(gè)?關(guān)閉?是有條件的。
寫個(gè)例子
public
?
class
?
ShutdownHookTest
?
{
????
public
?
static
?
void
?
main
(
String
[]
?args
)
?
{
????????
System
.
out
.
println
(
"Main?thread?start"
);
????????
Thread
?
hook
?
=
?
new
?
Thread
(
new
?
MyShutdownHook
());
????????
Runtime
.
getRuntime
().
addShutdownHook
(
hook
);
????????
System
.
out
.
println
(
"Main?thread?end"
);
????
}
????
static
?
class
?
MyShutdownHook
?
implements
?
Runnable
?
{
????????
@Override
????????
public
?
void
?
run
()
?
{
????????????
System
.
out
.
println
(
"--?my?shutdown?hook?start?--"
);
????????????
try
?
{
????????????????
System
.
out
.
println
(
"--?do?hook?task?--"
);
????????????????
Thread
.
sleep
(
5
?
*
?
1000L
);
????????????????
System
.
out
.
println
(
"--?hook?task?finished?--"
);
????????????
}
?
catch
?
(
InterruptedException
?e
)
?
{
????????????????e
.
printStackTrace
();
????????????
}
????????????
System
.
out
.
println
(
"--?my?shutdown?hook?end?--"
);
????????
}
????
}
}
使用?javac?編譯后運(yùn)行,得到的輸出結(jié)果是:
Main
?thread?start
Main
?thread?
end
--
?
my
?shutdown?hook?start?
--
--
?
do
?hook?task?
--
--
?hook?task?finished?
--
--
?
my
?shutdown?hook?
end
?
--
結(jié)果符合文章剛開始引文的第一個(gè)情況,當(dāng)程序的正常退出時(shí)會(huì)執(zhí)行注冊(cè)的鉤子。也就是說(shuō),在程序主線程(實(shí)際上是所有的?demon線程)結(jié)束后,會(huì)啟動(dòng)執(zhí)行鉤子線程。
程序非執(zhí)行完成推出的例子
稍微改一下上面的代碼:
public
?
class
?
ShutdownHookTest
?
{
????
public
?
static
?
void
?
main
(
String
[]
?args
)
?
throws
?
Exception
{
????????
System
.
out
.
println
(
"Main?thread?start"
);
????????
Thread
?
hook
?
=
?
new
?
Thread
(
new
?
MyShutdownHook
());
????????
Runtime
.
getRuntime
().
addShutdownHook
(
hook
);
????????
//?---?改了這里?---
????????
Thread
.
sleep
(
120
?
*
?
1000L
);
????????
//?--------------
????????
System
.
out
.
println
(
"Main?thread?end"
);
????
}
????
static
?
class
?
MyShutdownHook
?
implements
?
Runnable
?
{
????????
@Override
????????
public
?
void
?
run
()
?
{
????????????
System
.
out
.
println
(
"--?my?shutdown?hook?start?--"
);
????????????
try
?
{
????????????????
System
.
out
.
println
(
"--?do?hook?task?--"
);
????????????????
Thread
.
sleep
(
5
?
*
?
1000L
);
????????????????
System
.
out
.
println
(
"--?hook?task?finished?--"
);
????????????
}
?
catch
?
(
InterruptedException
?e
)
?
{
????????????????e
.
printStackTrace
();
????????????
}
????????????
System
.
out
.
println
(
"--?my?shutdown?hook?end?--"
);
????????
}
????
}
}
同樣使用?javac?編譯后運(yùn)行。不同的是,在程序啟動(dòng)后按?ctrl + c?停止程序,將得到下面的輸出:
Main
?thread?start
--
?
my
?shutdown?hook?start?
--
--
?
do
?hook?task?
--
--
?hook?task?finished?
--
--
?
my
?shutdown?hook?
end
?
--
這個(gè)結(jié)果也符合文章開始引文的第二個(gè)情況。當(dāng)虛擬機(jī)用為用戶輸入?^C?時(shí),虛擬機(jī)會(huì)調(diào)用已注冊(cè)的鉤子。
另外因?yàn)橐蔡岬搅水?dāng)用戶注銷和系統(tǒng)關(guān)閉時(shí)也會(huì)調(diào)用已注冊(cè)的鉤子,這里就不做驗(yàn)證了。
鉤子不能執(zhí)行的情況
同樣是上面的例子。程序在啟動(dòng)后,打開任務(wù)管理器(Windows),找到對(duì)應(yīng)的進(jìn)程并結(jié)束。這時(shí)控制臺(tái)的輸出為:
Main
?thread?start
從輸出可以看到,鉤子并沒有執(zhí)行。
這就說(shuō)明,在虛擬機(jī)中止?(注意:這里的中止不同于退出或停止,是指異常的break)?的情況下,鉤子不會(huì)被調(diào)用執(zhí)行。
javadoc?也給出了這種情況的說(shuō)明:
在極少數(shù)虛擬機(jī)被外部中止的情況下,例如:
??在 Unix 上使用 SIGKILL 信號(hào)
??在 Windows 上使用 TerminateProcess 調(diào)用
??執(zhí)行本地方法出錯(cuò)
也就是說(shuō),虛擬機(jī)在沒有干凈地關(guān)閉的情況下停止運(yùn)行,虛擬機(jī)則不能保證是否正確的運(yùn)行關(guān)機(jī)鉤子。
移除鉤子
移除鉤子使用方法?public boolean removeShutdownHook(Thread hook)?即可。
使用場(chǎng)景
看到這個(gè)特性,第一個(gè)想到的場(chǎng)景就是。程序在關(guān)閉時(shí)可以記錄一個(gè)日志或發(fā)送一個(gè)通知。
當(dāng)然,這個(gè)基于這個(gè)特性,可以定制出來(lái)很多使用場(chǎng)景。
但是,鑒于這個(gè)鉤子的執(zhí)行時(shí)機(jī),就有很多需要注意的地方:
-
??關(guān)機(jī)鉤子(shutdown hook)必須是一個(gè)已初始化但未啟動(dòng)的線程
-
??如果注冊(cè)了多個(gè)鉤子,則不能保證這些鉤子的執(zhí)行順序,他們是同時(shí)開始的
-
??當(dāng)虛擬機(jī)關(guān)機(jī)序列開始,則無(wú)法在注冊(cè)或取消鉤子
-
??對(duì)于鉤子的線程的編寫,應(yīng)該是線程安全的,并盡可能地避免出現(xiàn)死鎖
-
??鉤子的執(zhí)行時(shí)間不應(yīng)過長(zhǎng),也不應(yīng)該添加任何用戶交互功能
-
??這時(shí)因?yàn)楫?dāng)用戶注銷或者關(guān)機(jī)時(shí),底層操作系統(tǒng)可能只允許有限的固定時(shí)間來(lái)關(guān)閉和退出虛擬機(jī)
-
最后
文章就寫到這里。
這篇文章沒啥深入的探究,只是突然發(fā)現(xiàn)了一個(gè)之前未曾注意到的功能,簡(jiǎn)單的做一下記錄和測(cè)試。
大家如果要在生產(chǎn)環(huán)境中使用,要是場(chǎng)景復(fù)雜還是慎重些,上一章節(jié)的那些注意事項(xiàng)都需要考慮考慮。
---- END ----歡迎關(guān)注我的公眾號(hào)“須彌零一”,原創(chuàng)技術(shù)文章第一時(shí)間推送。
