Java 钩子程序

简介

触发的时机有:

  1. 当所有的非deamon线程(守护线程)结束, 或者调用了Systrem.exit()方法 而导致的程序正常的退出
  2. JVM收到需要关闭自己的信号(比如SIGINT、SIGTERM等,但像SIGKILL,JVM就没有机会去处理了),也或者发生如系统关闭这种不可阻挡的事件。

对于addShutdownHook中的钩子代码,也是有一些要注意的地方,下面列举几点:

  1. 关闭钩子可以注册多个,在关闭JVM时就会起多个线程来运行钩子。通常来说,一个钩子就足够了,但如果需要启用多个钩子,就需要注意并发带来的问题。
  2. 钩子里也要注意对异常的处理,如果不幸抛出了异常,那么钩子的执行序列就会被终止。
  3. 在钩子运行期间,工作线程也在运行,需要考虑到工作线程是否会对钩子的执行带来影响
  4. 钩子里的代码尽可能简洁,否则当像系统关闭等情景可能钩子来不及运行完JVM就被退出了。

信号触发

使信号触发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!!!");
        }
    }
}

运行钩子程序

nohup java HookTest &

关闭程序

kill HookTest_PID

我们可以在nohup程序中看到Hook execute!!!输出

我从JVMs and kill signals看到一篇博客, 这个上面总结了哪些信号会导致JVM运行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          -           -           触发 JVM dump threads / stack-traces
4 (SIGILL)      yes         no          134         触发 JVM 输出一个 core dump 文件, 同时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

内存溢出触发

测试JVM栈溢出后调用钩子程序

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!!!");
        }
    }
}

运行后输出为

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>

为了测试在更加复杂的环境下, 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();
                }
            }
        }
    }
}

运行后的输出结果为

ζ 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
...

正常结束触发

测试程序正常结束后也会调用钩子程序

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!!!");
        }
    }
}

运行结果为

D:\testOOM>java HookTest
Hook execute!!!

D:\testOOM>

调用exit()触发

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!!!");
        }
    }
}

运行结果为

D:\testOOM>java HookTest
Hook execute!!!

D:\testOOM>

不被触发

再google上找到了一篇这样的文章Know the JVM Series: Shutdown Hooks里面介绍了钩子程序在什么情况下不会执行
尽管上面列举出了N多触发钩子程序的示例, 但是并不保证这个钩子程序总是能被触发执行的, 例如

  • JVM内部发生错误, 可能还没有来得及触发钩子程序, JVM就挂掉了(JVM 发生内部错误, 有没有日志呢?)
  • 还有上面我们给出的那个信号表, 如果操作系统发送出上面的信号的话, 同样的, JVM没有执行钩子程序就退出了
  • 还有调用Runime.halt()函数也不会执行钩子程序

还有一种情况是, 当操作系统向进程发送一个SIGTERM信号之后, 如果进程没有在指定的时间之内关闭, 那么操作系统会强制将该进程杀掉, 如此一来钩子程序也不会得到完整的执行(因为钩子程序可能执行到一半就被操作系统杀死了). 因此不管是这篇文章还是JDK API都推荐不要在钩子程序里写复杂的业务逻辑, 避免产生死锁或者产生长时间的IO操作, 尽可能快地让钩子程序执行完毕.

在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.

哎,, 怎么着才能监控JVM挂掉的信息呢?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 一.线程安全性 线程安全是建立在对于对象状态访问操作进行管理,特别是对共享的与可变的状态的访问 解释下上面的话: ...
    黄大大吃不胖阅读 822评论 0 3
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 通常很多人会把瑜伽当做健身或运动的一种,甚至在一些词条分类中,瑜伽也是属于健身的项目' 这个误会可能已经很久了……...
    訪問权com阅读 853评论 0 3