欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Boot應(yīng)用關(guān)閉分析詳解

 更新時(shí)間:2024年12月21日 11:34:34   作者:程序猿進(jìn)階  
本文挖掘了Spring Boot的關(guān)閉方式,并列舉了關(guān)閉方式,從原理、源碼的角度闡述了Spring Boot的關(guān)閉代碼及擴(kuò)展點(diǎn),感興趣的朋友一起看看吧

一、使用spring容器的close方法關(guān)閉。

可通過(guò)在代碼中獲取SpringContext并調(diào)用close方法去關(guān)閉容器。

使用SpringApplication的exit方法。

public static int exit(ApplicationContext context,
            ExitCodeGenerator... exitCodeGenerators) {
        Assert.notNull(context, "Context must not be null");
        int exitCode = 0;
        try {
            try {
                //獲取ExitCodeGenerator的Bean并用ExitCodeGenerators管理
                ExitCodeGenerators generators = new ExitCodeGenerators();
                Collection<ExitCodeGenerator> beans = context
                        .getBeansOfType(ExitCodeGenerator.class).values();
                generators.addAll(exitCodeGenerators);
                generators.addAll(beans);
                exitCode = generators.getExitCode();
                if (exitCode != 0) {
                    // 發(fā)布ExitCodeEvent事件
                    context.publishEvent(new ExitCodeEvent(context, exitCode));
                }
            }
            finally {
                close(context);
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
            exitCode = (exitCode != 0) ? exitCode : 1;
        }
        return exitCode;
    }

上述代碼,總的來(lái)說(shuō)就是,獲取ExitCodeGenerator的Bean并用ExitCodeGenerators管理, 注意getExitCode()的實(shí)現(xiàn)是取ExitCodeGenerator集合中最大的exitCode作為最終exitCode,

最后,關(guān)閉容器。

二、exitCode

SpringApplication#exit方法返回的exitCode還需要自行調(diào)用System#exit方法去指定。 該System#exit(int code)的參數(shù),能被父進(jìn)程獲取并使用。一般按照慣例0為程序正常退出,非0位不正常退出。 我寫(xiě)了的運(yùn)行demo:

@Slf4j
@SpringBootApplication
public class ApplicationMainShutDownBySpringApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = new SpringApplicationBuilder(ApplicationMainShutDownBySpringApplication.class).build().run(args);
        int exitCode = SpringApplication.exit(ctx);
        log.info("exitCode is {}!", exitCode);
        System.exit(exitCode);
    }
    @Bean
    public ExitCodeGenerator exitCodeGenerator() {
        return () -> 10;
    }
}

執(zhí)行window bat:
java -jar spring-boot-mvc-shutdown-demo.jar & echo %ERRORLEVEL%

省略其他日志最終輸出:
10

可以看最終輸出是:10。

二、使用Actuator的shutdown http接口或JMX

可參考Actuator包的ShutdownEndpoint,實(shí)質(zhì)上是調(diào)用spring容器的close方法關(guān)閉的。

http方式關(guān)閉:

JMX方式關(guān)閉:

三、kill進(jìn)程

一般的kill(kill -15)會(huì)觸發(fā)應(yīng)用在refreshContext時(shí)(并且SpringApplication實(shí)例的registerShutdownHook為true時(shí))加上的注冊(cè)到JVM的shutdownhook。

public void registerShutdownHook() {
    if (this.shutdownHook == null) {
        // No shutdown hook registered yet.
        this.shutdownHook = new Thread() {
            @Override
            public void run() {
                synchronized (startupShutdownMonitor) {
                    doClose();
                }
            }
        };
        Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}

四、pidFile生成

spring boot提供ApplicationPidFileWriter類(lèi),將運(yùn)行時(shí)pid寫(xiě)入指定文件中。
應(yīng)用場(chǎng)景:可供kill使用。kill $(cat application.pid)

添加步驟:

【1】增加監(jiān)聽(tīng)器

org.springframework.context.ApplicationListener=\
org.springframework.boot.context.ApplicationPidFileWriter

【2】配置項(xiàng)

spring:
pid:
 fail-on-write-error: true
 #可通過(guò)此文件里的pid去關(guān)閉應(yīng)用
 file: ./application.pid

五、spring容器close代碼分析

這里對(duì)容器關(guān)閉進(jìn)行一些分析,以注釋的形式寫(xiě)在下面。

    /**
     * Close this application context, destroying all beans in its bean factory.
     * <p>Delegates to {@code doClose()} for the actual closing procedure.
     * Also removes a JVM shutdown hook, if registered, as it's not needed anymore.
     * @see #doClose()
     * @see #registerShutdownHook()
     */
    @Override
    public void close() {
        synchronized (this.startupShutdownMonitor) {
            //委托給鉤子方法doClose去做
            doClose();
            // If we registered a JVM shutdown hook, we don't need it anymore now:
            // We've already explicitly closed the context.
            if (this.shutdownHook != null) {
                try {
                    //去掉shutdown hook
                    Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
                }
                catch (IllegalStateException ex) {
                    // ignore - VM is already shutting down
                }
            }
        }
    }
    protected void doClose() {
        // Check whether an actual close attempt is necessary...
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing " + this);
            }
            LiveBeansView.unregisterApplicationContext(this);
            try {
                // Publish shutdown event.
                //發(fā)布事件,有需要,可寫(xiě)ApplicationListener對(duì)ContextClosedEvent事件進(jìn)行監(jiān)聽(tīng),在容器關(guān)閉時(shí)執(zhí)行自定義的操作
                publishEvent(new ContextClosedEvent(this));
            }
            catch (Throwable ex) {
                logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
            }
            // Stop all Lifecycle beans, to avoid delays during individual destruction.
            //觸發(fā)lifecycle bean的關(guān)閉周期
            if (this.lifecycleProcessor != null) {
                try {
                    this.lifecycleProcessor.onClose();
                }
                catch (Throwable ex) {
                    logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
                }
            }
            // Destroy all cached singletons in the context's BeanFactory.
            // 期間會(huì)觸發(fā)@PreDestroy、DisposableBean接口方法、@Bean的destroyMethod等等,具體執(zhí)行順序請(qǐng)參照BeanFactory接口的JavaDoc,里面定義了初始化和銷(xiāo)毀期的方法執(zhí)行順序。
            destroyBeans();
            // Close the state of this context itself.
            //目前沒(méi)做什么操作
            closeBeanFactory();
            // Let subclasses do some final clean-up if they wish...
            // 模板方法,比如,AnnotationConfigServletApplicationContext會(huì)觸發(fā)tomcat服務(wù)器的關(guān)閉和釋放
            onClose();
            // 重置listeners為初始狀態(tài),因?yàn)樵谌萜鲉?dòng)過(guò)程中會(huì)對(duì)ApplicationListener做了一些更改
            if (this.earlyApplicationListeners != null) {
                this.applicationListeners.clear();
                this.applicationListeners.addAll(this.earlyApplicationListeners);
            }
            // 將容器的激活狀態(tài)設(shè)為false
            this.active.set(false);
        }

整體流程
列出關(guān)閉流程:

五、容器關(guān)閉拓展點(diǎn)

上面的示例,容器關(guān)閉時(shí)的日志如下,

  INFO 241764 --- [           main] .w.s.s.ApplicationMainShutDownByActuator : Actuator shutdown result: {"message":"Shutting down, bye..."}
  INFO 241764 --- [      Thread-25] s.s.ApplicationContextCloseEventListener : event: org.springframework.context.event.ContextClosedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2f48b3d2, started on Sat Jun 27 01:22:57 CST 2020], source: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2f48b3d2, started on Sat Jun 27 01:22:57 CST 2020
  INFO 241764 --- [      Thread-25] n.t.d.s.w.s.s.spring.LoggingLifeCycle    : In Life cycle bean stop().
  INFO 241764 --- [      Thread-25] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
  INFO 241764 --- [      Thread-25] n.t.d.s.w.s.shutdown.bean.SimpleBean     : @PreDestroy!
  INFO 241764 --- [      Thread-25] n.t.d.s.w.s.shutdown.bean.SimpleBean     : DisposableBean is destroying!
  INFO 241764 --- [      Thread-25] n.t.d.s.w.s.shutdown.bean.SimpleBean     : On my way to destroy!

可看到一般可供使用的容器關(guān)閉時(shí)拓展點(diǎn)不多,分別有這兩個(gè):
1、監(jiān)聽(tīng)ContextClosedEvent事件,對(duì)應(yīng)例子是demo中的ApplicationContextCloseEventListener類(lèi)。
2、LifeCycle/SmartLifeCycle的stop()方法,對(duì)應(yīng)例子是demo中的LoggingLifeCycle類(lèi)。

他們的觸發(fā)時(shí)機(jī)在上面的close代碼的分析中有注釋。

六、拓展探究:kill與kill -9的區(qū)別

為什么說(shuō)這個(gè)呢,作為開(kāi)發(fā)一般我們知道kill的時(shí)候,Spring Boot程序可以執(zhí)行應(yīng)用退出相關(guān)的代碼,而kill -9則不能。任意使用kill -9,有時(shí)會(huì)造成一些問(wèn)題, 比如,一些執(zhí)行中的數(shù)據(jù)不能及時(shí)保存等等。

上面已經(jīng)說(shuō)到了,kill可以觸發(fā)java程序的shutdownhook,從而觸發(fā)spring容器的優(yōu)雅關(guān)閉。
這里,我不僅想討論shutdownhook怎么注冊(cè),怎樣觸發(fā),我把問(wèn)題放大了一點(diǎn):kill和kill -9在操作系統(tǒng)和JVM層面來(lái)看分別有什么不同,各自做了什么?

先看看命令的意思,以Ubuntu為例,執(zhí)行kill -l,如下:

kill命令不帶參數(shù),默認(rèn)是-15(SIGTERM),而kill -9是SIGKILL。

SIGTERM
The SIGTERM signal is sent to a process to request its termination. Unlike the SIGKILL signal, it can be caught and interpreted or ignored by the process. This allows the process to perform nice termination releasing resources and saving state if appropriate. SIGINT is nearly identical to SIGTERM.
SIGKILL
The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon receiving this signal. The following exceptions apply: Zombie processes cannot be killed since they are already dead and waiting for their parent processes to reap them. Processes that are in the blocked state will not die until they wake up again. The init process is special: It does not get signals that it does not want to handle, and thus it can ignore SIGKILL.[10] An exception from this rule is while init is ptraced on Linux.[11][12] An uninterruptibly sleeping process may not terminate (and free its resources) even when sent SIGKILL. This is one of the few cases in which a UNIX system may have to be rebooted to solve a temporary software problem. SIGKILL is used as a last resort when terminating processes in most system shutdown procedures if it does not voluntarily exit in response to SIGTERM. To speed the computer shutdown procedure, Mac OS X 10.6, aka Snow Leopard, will send SIGKILL to applications that have marked themselves “clean” resulting in faster shutdown times with, presumably, no ill effects.[13] The command killall -9 has a similar, while dangerous effect, when executed e.g. in Linux; it doesn’t let programs save unsaved data. It has other options, and with none, uses the safer SIGTERM signal.

七、那么一個(gè)進(jìn)程(包括Java進(jìn)程)是怎么處理信號(hào)的呢?

第一篇文章從內(nèi)核源碼的跟蹤,陳述了kill -9的操作系統(tǒng)執(zhí)行機(jī)制。

信號(hào)是異步的,信號(hào)的接收不是由用戶(hù)進(jìn)程來(lái)完成的,而是由內(nèi)核代理。 當(dāng)一個(gè)進(jìn)程P2向另一個(gè)進(jìn)程P1發(fā)送信號(hào)后,內(nèi)核接受到信號(hào),并將其放在P1的信號(hào)隊(duì)列當(dāng)中。當(dāng)P1再次陷入內(nèi)核態(tài)時(shí),會(huì)檢查信號(hào)隊(duì)列,并根據(jù)相應(yīng)的信號(hào)調(diào)取相應(yīng)的信號(hào)處理函數(shù)。

特別說(shuō)下,一個(gè)點(diǎn):
在如下信號(hào)處理代碼中可知,
如果是強(qiáng)制信號(hào)(比如SIGKILL(kill -9)),不走掛載pending隊(duì)列的流程,直接快速路徑優(yōu)先處理。 然后,在內(nèi)核層面就給處理掉,不會(huì)發(fā)送到進(jìn)程。

static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
            int group, int from_ancestor_ns)
{
    struct sigpending *pending;
    struct sigqueue *q;
    int override_rlimit;
    int ret = 0, result;
    assert_spin_locked(&t->sighand->siglock);
    result = TRACE_SIGNAL_IGNORED;
    // (1)判斷是否可以忽略信號(hào)
    if (!prepare_signal(sig, t,
            from_ancestor_ns || (info == SEND_SIG_FORCED)))
        goto ret;
    // (2)選擇信號(hào)pending隊(duì)列
    // 線(xiàn)程組共享隊(duì)列(t->signal->shared_pending) or 進(jìn)程私有隊(duì)列(t->pending)
    pending = group ? &t->signal->shared_pending : &t->pending;
    /*
     * Short-circuit ignored signals and support queuing
     * exactly one non-rt signal, so that we can get more
     * detailed information about the cause of the signal.
     */
    result = TRACE_SIGNAL_ALREADY_PENDING;
    // (3)如果信號(hào)是常規(guī)信號(hào)(regular signal),且已經(jīng)在pending隊(duì)列中,則忽略重復(fù)信號(hào);
    // 另外一方面也說(shuō)明了,如果是實(shí)時(shí)信號(hào),盡管信號(hào)重復(fù),但還是要加入pending隊(duì)列;
    // 實(shí)時(shí)信號(hào)的多個(gè)信號(hào)都需要能被接收到。
    if (legacy_queue(pending, sig))
        goto ret;
    result = TRACE_SIGNAL_DELIVERED;
    /*
     * fast-pathed signals for kernel-internal things like SIGSTOP
     * or SIGKILL.
     */
    // (4)如果是強(qiáng)制信號(hào)(SEND_SIG_FORCED),不走掛載pending隊(duì)列的流程,直接快速路徑優(yōu)先處理。
    if (info == SEND_SIG_FORCED)
        goto out_set;
    /*
     * Real-time signals must be queued if sent by sigqueue, or
     * some other real-time mechanism.  It is implementation
     * defined whether kill() does so.  We attempt to do so, on
     * the principle of least surprise, but since kill is not
     * allowed to fail with EAGAIN when low on memory we just
     * make sure at least one signal gets delivered and don't
     * pass on the info struct.
     */
    // (5)符合條件的特殊信號(hào)可以突破siganl pending隊(duì)列的大小限制(rlimit)
    // 否則在隊(duì)列滿(mǎn)的情況下,丟棄信號(hào)
    // signal pending隊(duì)列大小rlimit的值可以通過(guò)命令"ulimit -i"查看
    if (sig < SIGRTMIN)
        override_rlimit = (is_si_special(info) || info->si_code >= 0);
    else
        override_rlimit = 0;
    // (6)沒(méi)有ignore的信號(hào),加入到pending隊(duì)列中。
    q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
        override_rlimit);
    if (q) {
        list_add_tail(&q->list, &pending->list);
        switch ((unsigned long) info) {
        case (unsigned long) SEND_SIG_NOINFO:
            q->info.si_signo = sig;
            q->info.si_errno = 0;
            q->info.si_code = SI_USER;
            q->info.si_pid = task_tgid_nr_ns(current,
                            task_active_pid_ns(t));
            q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
            break;
        case (unsigned long) SEND_SIG_PRIV:
            q->info.si_signo = sig;
            q->info.si_errno = 0;
            q->info.si_code = SI_KERNEL;
            q->info.si_pid = 0;
            q->info.si_uid = 0;
            break;
        default:
            copy_siginfo(&q->info, info);
            if (from_ancestor_ns)
                q->info.si_pid = 0;
            break;
        }
        userns_fixup_signal_uid(&q->info, t);
    } else if (!is_si_special(info)) {
        if (sig >= SIGRTMIN && info->si_code != SI_USER) {
            /*
             * Queue overflow, abort.  We may abort if the
             * signal was rt and sent by user using something
             * other than kill().
             */
            result = TRACE_SIGNAL_OVERFLOW_FAIL;
            ret = -EAGAIN;
            goto ret;
        } else {
            /*
             * This is a silent loss of information.  We still
             * send the signal, but the *info bits are lost.
             */
            result = TRACE_SIGNAL_LOSE_INFO;
        }
    }
out_set:
    signalfd_notify(t, sig);
    // (7)更新pending->signal信號(hào)集合中對(duì)應(yīng)的bit
    sigaddset(&pending->signal, sig);
    // (8)選擇合適的進(jìn)程來(lái)響應(yīng)信號(hào),如果需要并喚醒對(duì)應(yīng)的進(jìn)程
    complete_signal(sig, t, group);
ret:
    trace_signal_generate(sig, info, t, group, result);
    return ret;
}
| →
static bool prepare_signal(int sig, struct task_struct *p, bool force)
{
    struct signal_struct *signal = p->signal;
    struct task_struct *t;
    sigset_t flush;
    if (signal->flags & (SIGNAL_GROUP_EXIT | SIGNAL_GROUP_COREDUMP)) {
        // (1.1)如果進(jìn)程正在處于SIGNAL_GROUP_COREDUMP,則當(dāng)前信號(hào)被忽略
        if (signal->flags & SIGNAL_GROUP_COREDUMP) {
            pr_debug("[%d:%s] is in the middle of doing coredump so skip sig %d\n", p->pid, p->comm, sig);
            return 0;
        }
        /*
         * The process is in the middle of dying, nothing to do.
         */
    } else if (sig_kernel_stop(sig)) {
        // (1.2)如果當(dāng)前是stop信號(hào),則移除線(xiàn)程組所有線(xiàn)程pending隊(duì)列中的SIGCONT信號(hào)
        /*
         * This is a stop signal.  Remove SIGCONT from all queues.
         */
        siginitset(&flush, sigmask(SIGCONT));
        flush_sigqueue_mask(&flush, &signal->shared_pending);
        for_each_thread(p, t)
            flush_sigqueue_mask(&flush, &t->pending);
    } else if (sig == SIGCONT) {
        unsigned int why;
        // (1.3)如果當(dāng)前是SIGCONT信號(hào),則移除線(xiàn)程組所有線(xiàn)程pending隊(duì)列中的stop信號(hào),并喚醒stop進(jìn)程
        /*
         * Remove all stop signals from all queues, wake all threads.
         */
        siginitset(&flush, SIG_KERNEL_STOP_MASK);
        flush_sigqueue_mask(&flush, &signal->shared_pending);
        for_each_thread(p, t) {
            flush_sigqueue_mask(&flush, &t->pending);
            task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING);
            if (likely(!(t->ptrace & PT_SEIZED)))
                wake_up_state(t, __TASK_STOPPED);
            else
                ptrace_trap_notify(t);
        }
        /*
         * Notify the parent with CLD_CONTINUED if we were stopped.
         *
         * If we were in the middle of a group stop, we pretend it
         * was already finished, and then continued. Since SIGCHLD
         * doesn't queue we report only CLD_STOPPED, as if the next
         * CLD_CONTINUED was dropped.
         */
        why = 0;
        if (signal->flags & SIGNAL_STOP_STOPPED)
            why |= SIGNAL_CLD_CONTINUED;
        else if (signal->group_stop_count)
            why |= SIGNAL_CLD_STOPPED;
        if (why) {
            /*
             * The first thread which returns from do_signal_stop()
             * will take ->siglock, notice SIGNAL_CLD_MASK, and
             * notify its parent. See get_signal_to_deliver().
             */
            signal->flags = why | SIGNAL_STOP_CONTINUED;
            signal->group_stop_count = 0;
            signal->group_exit_code = 0;
        }
    }
    // (1.4)進(jìn)一步判斷信號(hào)是否會(huì)被忽略
    return !sig_ignored(p, sig, force);
}
|| →
static int sig_ignored(struct task_struct *t, int sig, bool force)
{
    /*
     * Blocked signals are never ignored, since the
     * signal handler may change by the time it is
     * unblocked.
     */
    // (1.4.1)如果信號(hào)被blocked,不會(huì)被忽略
    if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig))
        return 0;
    // (1.4.2)進(jìn)一步判斷信號(hào)的忽略條件
    if (!sig_task_ignored(t, sig, force))
        return 0;
    /*
     * Tracers may want to know about even ignored signals.
     */
    // (1.4.3)信號(hào)符合忽略條件,且沒(méi)有被trace,則信號(hào)被忽略
    return !t->ptrace;
}
||| →
static int sig_task_ignored(struct task_struct *t, int sig, bool force)
{
    void __user *handler;
    // (1.4.2.1)提取信號(hào)的操作函數(shù)
    handler = sig_handler(t, sig);
    // (1.4.2.2)如果符合條件,信號(hào)被忽略
    if (unlikely(t->signal->flags & SIGNAL_UNKILLABLE) &&
            handler == SIG_DFL && !force)
        return 1;
    // (1.4.2.3)
    return sig_handler_ignored(handler, sig);
}
|||| →
static int sig_handler_ignored(void __user *handler, int sig)
{
    /* Is it explicitly or implicitly ignored? */
    // (1.4.2.3.1)如果信號(hào)操作函數(shù)是忽略SIG_IGN,或者操作函數(shù)是默認(rèn)SIG_DFL但是默認(rèn)動(dòng)作是忽略
    // 默認(rèn)動(dòng)作是忽略的信號(hào)包括:
    // #define SIG_KERNEL_IGNORE_MASK (\
    //    rt_sigmask(SIGCONT)   |  rt_sigmask(SIGCHLD)   | \
    //    rt_sigmask(SIGWINCH)  |  rt_sigmask(SIGURG)    )
    // 忽略這一類(lèi)信號(hào)
    return handler == SIG_IGN ||
        (handler == SIG_DFL && sig_kernel_ignore(sig));
}
| →
static void complete_signal(int sig, struct task_struct *p, int group)
{
    struct signal_struct *signal = p->signal;
    struct task_struct *t;
    /*
     * Now find a thread we can wake up to take the signal off the queue.
     *
     * If the main thread wants the signal, it gets first crack.
     * Probably the least surprising to the average bear.
     */
    // (8.1)判斷當(dāng)前線(xiàn)程是否符合響應(yīng)信號(hào)的條件
    if (wants_signal(sig, p))
        t = p;
    else if (!group || thread_group_empty(p))
        // (8.2)如果信號(hào)是發(fā)給單線(xiàn)程的,直接返回
        /*
         * There is just one thread and it does not need to be woken.
         * It will dequeue unblocked signals before it runs again.
         */
        return;
    else {
        /*
         * Otherwise try to find a suitable thread.
         */
        // (8.3)在當(dāng)前線(xiàn)程組中挑出一個(gè)符合響應(yīng)信號(hào)條件的線(xiàn)程
        // 從signal->curr_target線(xiàn)程開(kāi)始查找
        t = signal->curr_target;
        while (!wants_signal(sig, t)) {
            t = next_thread(t);
            if (t == signal->curr_target)
                /*
                 * No thread needs to be woken.
                 * Any eligible threads will see
                 * the signal in the queue soon.
                 */
                return;
        }
        signal->curr_target = t;
    }
    /*
     * Found a killable thread.  If the signal will be fatal,
     * then start taking the whole group down immediately.
     */
    if (sig_fatal(p, sig) &&
        !(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&
        !sigismember(&t->real_blocked, sig) &&
        (sig == SIGKILL || !t->ptrace)) {
        /*
         * This signal will be fatal to the whole group.
         */
        if (!sig_kernel_coredump(sig)) {
            /*
             * Start a group exit and wake everybody up.
             * This way we don't have other threads
             * running and doing things after a slower
             * thread has the fatal signal pending.
             */
            signal->flags = SIGNAL_GROUP_EXIT;
            signal->group_exit_code = sig;
            signal->group_stop_count = 0;
            t = p;
            do {
                task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
                sigaddset(&t->pending.signal, SIGKILL);
                signal_wake_up(t, 1);
            } while_each_thread(p, t);
            return;
        }
    }
    /*
     * The signal is already in the shared-pending queue.
     * Tell the chosen thread to wake up and dequeue it.
     */
    // (8.4)喚醒挑選出的響應(yīng)信號(hào)的線(xiàn)程
    signal_wake_up(t, sig == SIGKILL);
    return;
}
|| →
static inline void ptrace_signal_wake_up(struct task_struct *t, bool resume)
{
    signal_wake_up_state(t, resume ? __TASK_TRACED : 0);
}
||| →
void signal_wake_up_state(struct task_struct *t, unsigned int state)
{
    // (8.4.1)設(shè)置thread_info->flags中的TIF_SIGPENDING標(biāo)志
    // ret_to_user()時(shí)會(huì)根據(jù)此標(biāo)志來(lái)調(diào)用do_notify_resume()
    set_tsk_thread_flag(t, TIF_SIGPENDING);
    /*
     * TASK_WAKEKILL also means wake it up in the stopped/traced/killable
     * case. We don't check t->state here because there is a race with it
     * executing another processor and just now entering stopped state.
     * By using wake_up_state, we ensure the process will wake up and
     * handle its death signal.
     */
    // (8.4.2)喚醒阻塞狀態(tài)為T(mén)ASK_INTERRUPTIBLE的信號(hào)響應(yīng)線(xiàn)程
    if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))
        kick_process(t);
}

八、JDK處理信號(hào)的相關(guān)代碼分析

由上一節(jié)可知,kill -9就不會(huì)到進(jìn)程這里。kill -15(默認(rèn)的kill)是JVM可是接收到的。這里說(shuō)下JDK的支持。

JDK在linux里用到的信號(hào)及用途有這些: Signals Used in Oracle Solaris and Linux | Java Platform, Standard Edition Troubleshooting Guide 其中,SIGTERM, SIGINT, SIGHUP支持了ShutdownHook。

JDK的掛載信號(hào)處理函數(shù)在這里。openjdk8|jsig.c

九、JDK掛載Shutdownhook的代碼分析

我們可以通過(guò)sun.misc.Signal類(lèi)掛載信號(hào)處理器。

Signal.handle(new Signal(“INT”), System.out::println);
其實(shí),JDK就是通過(guò)掛載信號(hào)處理器去執(zhí)行shutdownhook的。

留意,Terminator類(lèi)針對(duì)SIGINT、SIGTERM掛載了兩個(gè)信號(hào)處理器。

class Terminator {
    private static SignalHandler handler = null;
    /* Invocations of setup and teardown are already synchronized
     * on the shutdown lock, so no further synchronization is needed here
     */
    static void setup() {
        if (handler != null) return;
        SignalHandler sh = new SignalHandler() {
            public void handle(Signal sig) {
                Shutdown.exit(sig.getNumber() + 0200);
            }
        };
        handler = sh;
        // When -Xrs is specified the user is responsible for
        // ensuring that shutdown hooks are run by calling
        // System.exit()
        try {
            Signal.handle(new Signal("INT"), sh);
        } catch (IllegalArgumentException e) {
        }
        try {
            Signal.handle(new Signal("TERM"), sh);
        } catch (IllegalArgumentException e) {
        }
    }
    static void teardown() {
        /* The current sun.misc.Signal class does not support
         * the cancellation of handlers
         */
    }
}

在存在對(duì)應(yīng)信號(hào)而觸發(fā)執(zhí)行時(shí),實(shí)際執(zhí)行Shutdown#exit()方法。 進(jìn)一步跟蹤,在sequence()的runHooks()會(huì)執(zhí)行Runtime#addShutdownHook(Thread hook)添加進(jìn)來(lái)的Shutdownhook。

btw,我們可以看到Shutdown有兩個(gè)方法,一個(gè)是Shutdown#exit(int status), Shutdown#shutdown()。 當(dāng)最后一條非Daemon線(xiàn)程已經(jīng)執(zhí)行完了,JVM會(huì)通過(guò)JNI調(diào)用Shutdown#shutdown()方法。

所以,觸發(fā)shutdownhook執(zhí)行有兩個(gè)契機(jī),
1、一是JAVA程序收到SIGINT、SIGTERM信號(hào)量。
2、或者當(dāng)非Daemon線(xiàn)程都已經(jīng)執(zhí)行完了。

十、總結(jié)

本文挖掘了Spring Boot的關(guān)閉方式,并列舉了關(guān)閉方式,從原理、源碼的角度闡述了Spring Boot的關(guān)閉代碼及擴(kuò)展點(diǎn)。同時(shí),額外說(shuō)明了一些系統(tǒng)特性 和原理,比如,程序退出碼和信號(hào)機(jī)制。

到此這篇關(guān)于Spring Boot應(yīng)用關(guān)閉分析的文章就介紹到這了,更多相關(guān)Spring Boot應(yīng)用關(guān)閉內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springboot使用小工具之Lombok、devtools、Spring Initailizr詳解

    springboot使用小工具之Lombok、devtools、Spring Initailizr詳解

    這篇文章主要介紹了springboot使用小工具之Lombok、devtools、Spring Initailizr詳解,Lombok可以代替手寫(xiě)get、set、構(gòu)造方法等,需要idea裝插件lombok,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • Java中EnumMap代替序數(shù)索引代碼詳解

    Java中EnumMap代替序數(shù)索引代碼詳解

    這篇文章主要介紹了Java中EnumMap代替序數(shù)索引代碼詳解,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • 使用SpringBoot根據(jù)配置注入接口的不同實(shí)現(xiàn)類(lèi)(代碼演示)

    使用SpringBoot根據(jù)配置注入接口的不同實(shí)現(xiàn)類(lèi)(代碼演示)

    使用springboot開(kāi)發(fā)時(shí)經(jīng)常用到@Autowired和@Resource進(jìn)行依賴(lài)注入,但是當(dāng)我們一個(gè)接口對(duì)應(yīng)多個(gè)不同的實(shí)現(xiàn)類(lèi)的時(shí)候如果不進(jìn)行一下配置項(xiàng)目啟動(dòng)時(shí)就會(huì)報(bào)錯(cuò),那么怎么根據(jù)不同的需求注入不同的類(lèi)型呢,感興趣的朋友一起看看吧
    2022-06-06
  • 關(guān)于@Autowierd && @Resource 你真的了解嗎

    關(guān)于@Autowierd && @Resource 你真的了解嗎

    這篇文章主要介紹了關(guān)于@Autowierd && @Resource的具體使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • JAVA Map架構(gòu)和API介紹

    JAVA Map架構(gòu)和API介紹

    JAVA Map架構(gòu)和API介紹:Map、Map.Entry、AbstractMap、SortedMap、 NavigableMap、Dictionary。
    2013-11-11
  • SpringBoot使用JWT實(shí)現(xiàn)登錄驗(yàn)證的方法示例

    SpringBoot使用JWT實(shí)現(xiàn)登錄驗(yàn)證的方法示例

    這篇文章主要介紹了SpringBoot使用JWT實(shí)現(xiàn)登錄驗(yàn)證的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • SpringBoot項(xiàng)目中的視圖解析器問(wèn)題(兩種)

    SpringBoot項(xiàng)目中的視圖解析器問(wèn)題(兩種)

    SpringBoot官網(wǎng)推薦使用HTML視圖解析器,但是根據(jù)個(gè)人的具體業(yè)務(wù)也有可能使用到JSP視圖解析器,所以本文介紹了兩種視圖解析器,感興趣的可以了解下
    2020-06-06
  • SpringBoot中創(chuàng)建的AOP不生效的原因及解決

    SpringBoot中創(chuàng)建的AOP不生效的原因及解決

    這篇文章主要介紹了SpringBoot中創(chuàng)建的AOP不生效的原因及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • SpringMVC通過(guò)注解獲得參數(shù)的實(shí)例

    SpringMVC通過(guò)注解獲得參數(shù)的實(shí)例

    下面小編就為大家?guī)?lái)一篇SpringMVC通過(guò)注解獲得參數(shù)的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • Java使用HttpClient實(shí)現(xiàn)文件下載

    Java使用HttpClient實(shí)現(xiàn)文件下載

    這篇文章主要為大家詳細(xì)介紹了Java使用HttpClient實(shí)現(xiàn)文件下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08

最新評(píng)論