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挂掉的信息呢?

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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