美文网首页
java进程关闭事件监听

java进程关闭事件监听

作者: 想起个帅气的头像 | 来源:发表于2021-01-27 09:58 被阅读0次

    jvm进程如何感知关闭事件

    java.lang.Shutdown

    结束一个普通的java进程,一般来说可以让程序自行结束,也可以通过System.exit(n);来主动触发终止。

    如果是linux系统,还可以通过外部信号来终止进程。
    一般来说停止一个服务常用的方式就是kill -2 pid(ctrl + C)kill -9 pidkill -15 pid
    kill -9 可以认为操作系统从内核级别直接强行kill进程,对进程来说没有任何的准备,且无法监听-9信号。
    kill -2 和 -15 则是操作系统给该进程发送一个信号通知,告知应用主动关闭,应用可以监听并接收到信号,可以完成一些关闭回收等动作,然后自我停止。
    jvm专门有个Signal Dispatcher线程来接收信号。

    Shutdown针对这几类终止方式提供了两个处理方法。

      /* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon thread has finished.  
        Unlike the exit method, this method does not actually halt the VM. */
        static void shutdown() {
            synchronized (lock) {
                switch (state) {
                case RUNNING:       /* Initiate shutdown */
                    state = HOOKS;
                    break;
                case HOOKS:         /* Stall and then return */
                case FINALIZERS:
                    break;
                }
            }
            synchronized (Shutdown.class) {
                sequence();
            }
        }
    

    结合方法注释,当前进程如果所有的非守护线程执行完成,会由JNI DestroyJavaVM触发shutdown方法调用。此方法并没有halt(停止)VM。

      /* Invoked by Runtime.exit, which does all the security checks.
         * Also invoked by handlers for system-provided termination events, which should pass a nonzero status code.
         */
        static void exit(int status) {
            boolean runMoreFinalizers = false;
            synchronized (lock) {
                if (status != 0) runFinalizersOnExit = false;
                switch (state) {
                case RUNNING:       /* Initiate shutdown */
                    state = HOOKS;
                    break;
                case HOOKS:         /* Stall and halt */
                    break;
                case FINALIZERS:
                    if (status != 0) {
                        /* Halt immediately on nonzero status */
                        halt(status);
                    } else {
                        // Compatibility with old behavior: Run more finalizers and then halt
                        runMoreFinalizers = runFinalizersOnExit;
                    }
                    break;
                }
            }
            if (runMoreFinalizers) {
                runAllFinalizers();
                halt(status);
            }
            synchronized (Shutdown.class) {
                //Synchronize on the class object, causing any other thread that attempts to initiate shutdown to stall indefinitely
                sequence();
                halt(status);
            }
        }
    

    当在代码中调用了Runtime.exit(System.exit),会调用此exit方法,status是0表示正常退出,非0表示异常退出,此方法会主动halt VM。

    除了进程退出的处理方法外,在ShutDown类中,还定义了hook,允许我们在进程停止前,完成一些清场的操作。

      // The system shutdown hooks are registered with a predefined slot.
        // The list of shutdown hooks is as follows:
        // (0) Console restore hook
        // (1) Application hooks
        // (2) DeleteOnExit hook
        private static final int MAX_SYSTEM_HOOKS = 10;
        private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
    

    每个hook对应一个线程,会在add方法中添加,在runHooks中依次执行。

        static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
            synchronized (lock) {
                if (hooks[slot] != null)
                    throw new InternalError("Shutdown hook at slot " + slot + " already registered");
                    // ... 异常场景判断
                hooks[slot] = hook;
            }
        }
    
        /* Run all registered shutdown hooks */
        private static void runHooks() {
            for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
                try {
                    Runnable hook;
                    synchronized (lock) {
                        // acquire the lock to make sure the hook registered during shutdown is visible here.
                        currentRunningHook = i;
                        hook = hooks[i];
                    }
                    if (hook != null) hook.run();
                } catch(Throwable t) {
                    if (t instanceof ThreadDeath) {
                        ThreadDeath td = (ThreadDeath)t;
                        throw td;
                    }
                }
            }
        }
    

    如何添加自定义的shutdown hook

    Shutdown类属于系统的操作类,并没有暴露给应用层使用。如果我们想定义自己的shutdown hook,可以使用Runtime.getRuntime().addShutdownHook()

      public void addShutdownHook(Thread hook) {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                sm.checkPermission(new RuntimePermission("shutdownHooks"));
            }
            ApplicationShutdownHooks.add(hook);
        }
    

    这个方法调用了ApplicationShutdownHooks.add(hook)

    ApplicationShutdownHooks
    class ApplicationShutdownHooks {
        private static IdentityHashMap<Thread, Thread> hooks;
        static {
            try {
                Shutdown.add(1 /* shutdown hook invocation order */,
                    false /* not registered if shutdown in progress */,
                    new Runnable() {
                        public void run() {
                            runHooks();
                        }
                    }
                );
                hooks = new IdentityHashMap<>();
            } catch (IllegalStateException e) {
                // application shutdown hooks cannot be added if
                // shutdown is in progress.
                hooks = null;
            }
        }
        /* Add a new shutdown hook.  Checks the shutdown state and the hook itself, but does not do any security checks. */
        static synchronized void add(Thread hook) {
            if(hooks == null)
                throw new IllegalStateException("Shutdown in progress");
    
            if (hook.isAlive())
                throw new IllegalArgumentException("Hook already running");
    
            if (hooks.containsKey(hook))
                throw new IllegalArgumentException("Hook previously registered");
    
            hooks.put(hook, hook);
        }
        // ...
    }
    

    ApplicationShutdownHooks在类初始化过程完成了hook回调的注册,并初始化了IdentityHashMap,当有自定义的hook被添加时,缓存到map中。

    当shutdown被触发后,会通过hook回调来调用到runHooks()

        /* Iterates over all application hooks creating a new thread for each
         * to run in. Hooks are run concurrently and this method waits for
         * them to finish.
         */
        static void runHooks() {
            Collection<Thread> threads;
            synchronized(ApplicationShutdownHooks.class) {
                threads = hooks.keySet();
                hooks = null;
            }
    
            for (Thread hook : threads) {
                hook.start();
            }
            for (Thread hook : threads) {
                while (true) {
                    try {
                        hook.join();
                        break;
                    } catch (InterruptedException ignored) {
                    }
                }
            }
        }
    

    runHooks()会遍历启动的每一个hook线程,并通过join来等待所有hook执行完成。因为这个hook线程是并行操作的,所以这里无法保证hook的执行顺序。

    总结

    1. jvm进程的关闭会由JNI触发Shutdown类中的exit()shutdown(), 这两个方法会调用hook回调。
    2. 自定义的shutdown hook通过Runtime.getRuntime().addShutdownHook()添加到进程中。
    3. 多个自定义shutdown hook并行执行,不保证执行顺序。

    相关文章

      网友评论

          本文标题:java进程关闭事件监听

          本文链接:https://www.haomeiwen.com/subject/hdvozktx.html