簡介
觸發(fā)的時(shí)機(jī)有:
- 當(dāng)所有的非deamon線程(守護(hù)線程)結(jié)束, 或者調(diào)用了
Systrem.exit()
方法 而導(dǎo)致的程序正常的退出 - JVM收到需要關(guān)閉自己的信號(比如SIGINT、SIGTERM等烤咧,但像SIGKILL,JVM就沒有機(jī)會(huì)去處理了)琼娘,也或者發(fā)生如系統(tǒng)關(guān)閉這種不可阻擋的事件锰提。
對于addShutdownHook中的鉤子代碼嫌变,也是有一些要注意的地方璃饱,下面列舉幾點(diǎn):
- 關(guān)閉鉤子可以注冊多個(gè)胖笛,在關(guān)閉JVM時(shí)就會(huì)起多個(gè)線程來運(yùn)行鉤子眠副。通常來說画切,一個(gè)鉤子就足夠了,但如果需要啟用多個(gè)鉤子囱怕,就需要注意并發(fā)帶來的問題霍弹。
- 鉤子里也要注意對異常的處理,如果不幸拋出了異常娃弓,那么鉤子的執(zhí)行序列就會(huì)被終止典格。
- 在鉤子運(yùn)行期間,工作線程也在運(yùn)行台丛,需要考慮到工作線程是否會(huì)對鉤子的執(zhí)行帶來影響
- 鉤子里的代碼盡可能簡潔耍缴,否則當(dāng)像系統(tǒng)關(guān)閉等情景可能鉤子來不及運(yùn)行完JVM就被退出了。
信號觸發(fā)
使信號觸發(fā)JVM的鉤子程序
public class HookTest {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Hook());
while(true){}
}
static class Hook extends Thread{
@Override
public void run() {
System.out.println("Hook execute!!!");
}
}
}
運(yùn)行鉤子程序
nohup java HookTest &
關(guān)閉程序
kill HookTest_PID
我們可以在nohup程序中看到Hook execute!!!輸出
我從JVMs and kill signals看到一篇博客, 這個(gè)上面總結(jié)了哪些信號會(huì)導(dǎo)致JVM運(yùn)行Hook
signal shutdown runs hook exit code comment
default (15) yes yes 143 SIGTERM is the default unix kill signal
0 no - -
1 (SIGHUP) yes yes 129
2 (SIGINT) yes yes 130 SIGINT is the signal sent on ^C
3 (SIGQUIT) no - - 觸發(fā) JVM dump threads / stack-traces
4 (SIGILL) yes no 134 觸發(fā) JVM 輸出一個(gè) core dump 文件, 同時(shí)abort on trap 6
5 yes no 133 Makes the JVM exit with "Trace/BPT trap: 5"
6 (SIGABRT) yes no 134 Makes the JVM exit with "Abort trap: 6"
7 yes no 135 Makes the JVM exit with "EMT trap: 7"
8 (SIGFPE) yes no 134 Makes the JVM write a core dump and abort on trap 6
9 (SIGKILL) yes no 137 The JVM is forcibly killed (exits with "Killed: 9")
10 (SIGBUS) yes no 134 Emulates a "Bus Error"
11 (SIGSEGV) yes no 134 Emulates a "Segmentation fault"
12 yes no 140 Makes the JVM exit with "Bad system call: 12"
13 no - -
14 yes no 142 Makes the JVM exit with "Alarm clock: 14"
15 (SIGTERM) yes yes 143 This is the default unix kill signal
16 no - -
17 no - 145 Stops the application (sends it to the background), same as ^Z
18 no - 146 Stops the application (sends it to the background), same as ^Z
19 no - -
20 no - -
21 no - 149 Stops the application (sends it to the background), same as ^Z
22 no - 150 Stops the application (sends it to the background), same as ^Z
23 no - -
24 yes no 152 Makes the JVM exit with "Cputime limit exceeded: 24"
25 no - -
26 yes no 154 Makes the JVM exit with "Virtual timer expired: 26"
27 yes no 155 Makes the JVM exit with "Profiling timer expired: 27"
28 no - -
29 no - -
30 yes no 158 Makes the JVM exit with "User defined signal 1: 30"
31 yes no 134 Makes the JVM exit on Segmentation fault
內(nèi)存溢出觸發(fā)
測試JVM棧溢出后調(diào)用鉤子程序
public class HookTest {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Hook());
exec();
}
public static void exec() {
exec();
}
static class Hook extends Thread{
@Override
public void run() {
System.out.println("Hook execute!!!");
}
}
}
運(yùn)行后輸出為
D:\testOOM>java HookTest
Exception in thread "main" java.lang.StackOverflowError
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
...
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
at HookTest.exec(HookTest.java:9)
Hook execute!!!
D:\testOOM>
為了測試在更加復(fù)雜的環(huán)境下, Hook的使用情況, 看下面的測試代碼
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class HookTest {
private static Map<String, String> cache = new HashMap<>();
public static void main(String[] args) {
cache.put("abc", "abc");
Runtime.getRuntime().addShutdownHook(new Hook());
byte[] bytes = new byte[1024 * 1024 *1024 * 1024];
}
static class Hook extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(LocalDateTime.now());
System.out.println(" freeMemory : " + Runtime.getRuntime().freeMemory());
System.out.println(" maxMemory : " + Runtime.getRuntime().maxMemory());
System.out.println(" totalMemory : " + Runtime.getRuntime().totalMemory());
System.out.println(" currentThread name : " + Thread.currentThread().getName());
System.out.println(" cache size : " + cache.size());
cache.put(LocalDateTime.now().toString(), LocalDateTime.now().toString());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
運(yùn)行后的輸出結(jié)果為
ζ java HookTest
2016-07-09T16:12:12.479
freeMemory : 155922512
maxMemory : 2375024640
totalMemory : 160956416
currentThread name : Thread-0
cache size : 1
2016-07-09T16:12:13.480
freeMemory : 155922512
maxMemory : 2375024640
totalMemory : 160956416
currentThread name : Thread-0
cache size : 2
2016-07-09T16:12:14.480
freeMemory : 155922512
maxMemory : 2375024640
totalMemory : 160956416
currentThread name : Thread-0
cache size : 3
2016-07-09T16:12:15.480
freeMemory : 155922512
maxMemory : 2375024640
totalMemory : 160956416
currentThread name : Thread-0
cache size : 4
...
正常結(jié)束觸發(fā)
測試程序正常結(jié)束后也會(huì)調(diào)用鉤子程序
public class HookTest {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Hook());
}
static class Hook extends Thread{
@Override
public void run() {
System.out.println("Hook execute!!!");
}
}
}
運(yùn)行結(jié)果為
D:\testOOM>java HookTest
Hook execute!!!
D:\testOOM>
調(diào)用exit()觸發(fā)
public class HookTest {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Hook());
System.exit(0);
System.out.println("Main over");
}
static class Hook extends Thread{
@Override
public void run() {
System.out.println("Hook execute!!!");
}
}
}
運(yùn)行結(jié)果為
D:\testOOM>java HookTest
Hook execute!!!
D:\testOOM>
不被觸發(fā)
再google上找到了一篇這樣的文章Know the JVM Series: Shutdown Hooks里面介紹了鉤子程序在什么情況下不會(huì)執(zhí)行
盡管上面列舉出了N多觸發(fā)鉤子程序的示例, 但是并不保證這個(gè)鉤子程序總是能被觸發(fā)執(zhí)行的, 例如
- JVM內(nèi)部發(fā)生錯(cuò)誤, 可能還沒有來得及觸發(fā)鉤子程序, JVM就掛掉了(JVM 發(fā)生內(nèi)部錯(cuò)誤, 有沒有日志呢?)
- 還有上面我們給出的那個(gè)信號表, 如果操作系統(tǒng)發(fā)送出上面的信號的話, 同樣的, JVM沒有執(zhí)行鉤子程序就退出了
- 還有調(diào)用
Runime.halt()
函數(shù)也不會(huì)執(zhí)行鉤子程序
還有一種情況是, 當(dāng)操作系統(tǒng)向進(jìn)程發(fā)送一個(gè)SIGTERM
信號之后, 如果進(jìn)程沒有在指定的時(shí)間之內(nèi)關(guān)閉, 那么操作系統(tǒng)會(huì)強(qiáng)制將該進(jìn)程殺掉, 如此一來鉤子程序也不會(huì)得到完整的執(zhí)行(因?yàn)殂^子程序可能執(zhí)行到一半就被操作系統(tǒng)殺死了). 因此不管是這篇文章還是JDK API都推薦不要在鉤子程序里寫復(fù)雜的業(yè)務(wù)邏輯, 避免產(chǎn)生死鎖或者產(chǎn)生長時(shí)間的IO操作, 盡可能快地讓鉤子程序執(zhí)行完畢.
在oracle上的Design of the Shutdown Hooks API同樣見到這樣一句話,
Will shutdown hooks be run if the VM crashes?
If the VM crashes due to an error in native code then no guarantee can be made about whether or not the hooks will be run.
哎,, 怎么著才能監(jiān)控JVM掛掉的信息呢挽霉?