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

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

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

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

可通過在代碼中獲取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;
    }

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

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

二、exitCode

SpringApplication#exit方法返回的exitCode還需要自行調(diào)用System#exit方法去指定。 該System#exit(int code)的參數(shù),能被父進(jìn)程獲取并使用。一般按照慣例0為程序正常退出,非0位不正常退出。 我寫了的運行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,實質(zhì)上是調(diào)用spring容器的close方法關(guān)閉的。

http方式關(guān)閉:

JMX方式關(guān)閉:

三、kill進(jìn)程

一般的kill(kill -15)會觸發(fā)應(yīng)用在refreshContext時(并且SpringApplication實例的registerShutdownHook為true時)加上的注冊到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類,將運行時pid寫入指定文件中。
應(yīng)用場景:可供kill使用。kill $(cat application.pid)

添加步驟:

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

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

【2】配置項

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

五、spring容器close代碼分析

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

    /**
     * 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ā)布事件,有需要,可寫ApplicationListener對ContextClosedEvent事件進(jìn)行監(jiān)聽,在容器關(guān)閉時執(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.
            // 期間會觸發(fā)@PreDestroy、DisposableBean接口方法、@Bean的destroyMethod等等,具體執(zhí)行順序請參照BeanFactory接口的JavaDoc,里面定義了初始化和銷毀期的方法執(zhí)行順序。
            destroyBeans();
            // Close the state of this context itself.
            //目前沒做什么操作
            closeBeanFactory();
            // Let subclasses do some final clean-up if they wish...
            // 模板方法,比如,AnnotationConfigServletApplicationContext會觸發(fā)tomcat服務(wù)器的關(guān)閉和釋放
            onClose();
            // 重置listeners為初始狀態(tài),因為在容器啟動過程中會對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)閉拓展點

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

  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)閉時拓展點不多,分別有這兩個:
1、監(jiān)聽ContextClosedEvent事件,對應(yīng)例子是demo中的ApplicationContextCloseEventListener類。
2、LifeCycle/SmartLifeCycle的stop()方法,對應(yīng)例子是demo中的LoggingLifeCycle類。

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

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

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

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

先看看命令的意思,以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.

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

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

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

特別說下,一個點:
在如下信號處理代碼中可知,
如果是強(qiáng)制信號(比如SIGKILL(kill -9)),不走掛載pending隊列的流程,直接快速路徑優(yōu)先處理。 然后,在內(nèi)核層面就給處理掉,不會發(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)判斷是否可以忽略信號
    if (!prepare_signal(sig, t,
            from_ancestor_ns || (info == SEND_SIG_FORCED)))
        goto ret;
    // (2)選擇信號pending隊列
    // 線程組共享隊列(t->signal->shared_pending) or 進(jìn)程私有隊列(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)如果信號是常規(guī)信號(regular signal),且已經(jīng)在pending隊列中,則忽略重復(fù)信號;
    // 另外一方面也說明了,如果是實時信號,盡管信號重復(fù),但還是要加入pending隊列;
    // 實時信號的多個信號都需要能被接收到。
    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)制信號(SEND_SIG_FORCED),不走掛載pending隊列的流程,直接快速路徑優(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)符合條件的特殊信號可以突破siganl pending隊列的大小限制(rlimit)
    // 否則在隊列滿的情況下,丟棄信號
    // signal pending隊列大小rlimit的值可以通過命令"ulimit -i"查看
    if (sig < SIGRTMIN)
        override_rlimit = (is_si_special(info) || info->si_code >= 0);
    else
        override_rlimit = 0;
    // (6)沒有ignore的信號,加入到pending隊列中。
    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信號集合中對應(yīng)的bit
    sigaddset(&pending->signal, sig);
    // (8)選擇合適的進(jìn)程來響應(yīng)信號,如果需要并喚醒對應(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)前信號被忽略
        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信號,則移除線程組所有線程pending隊列中的SIGCONT信號
        /*
         * 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信號,則移除線程組所有線程pending隊列中的stop信號,并喚醒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)一步判斷信號是否會被忽略
    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)如果信號被blocked,不會被忽略
    if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig))
        return 0;
    // (1.4.2)進(jìn)一步判斷信號的忽略條件
    if (!sig_task_ignored(t, sig, force))
        return 0;
    /*
     * Tracers may want to know about even ignored signals.
     */
    // (1.4.3)信號符合忽略條件,且沒有被trace,則信號被忽略
    return !t->ptrace;
}
||| →
static int sig_task_ignored(struct task_struct *t, int sig, bool force)
{
    void __user *handler;
    // (1.4.2.1)提取信號的操作函數(shù)
    handler = sig_handler(t, sig);
    // (1.4.2.2)如果符合條件,信號被忽略
    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)如果信號操作函數(shù)是忽略SIG_IGN,或者操作函數(shù)是默認(rèn)SIG_DFL但是默認(rèn)動作是忽略
    // 默認(rèn)動作是忽略的信號包括:
    // #define SIG_KERNEL_IGNORE_MASK (\
    //    rt_sigmask(SIGCONT)   |  rt_sigmask(SIGCHLD)   | \
    //    rt_sigmask(SIGWINCH)  |  rt_sigmask(SIGURG)    )
    // 忽略這一類信號
    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)前線程是否符合響應(yīng)信號的條件
    if (wants_signal(sig, p))
        t = p;
    else if (!group || thread_group_empty(p))
        // (8.2)如果信號是發(fā)給單線程的,直接返回
        /*
         * 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)前線程組中挑出一個符合響應(yīng)信號條件的線程
        // 從signal->curr_target線程開始查找
        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)信號的線程
    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()時會根據(jù)此標(biāo)志來調(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)為TASK_INTERRUPTIBLE的信號響應(yīng)線程
    if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))
        kick_process(t);
}

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

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

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

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

九、JDK掛載Shutdownhook的代碼分析

我們可以通過sun.misc.Signal類掛載信號處理器。

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

留意,Terminator類針對SIGINT、SIGTERM掛載了兩個信號處理器。

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
         */
    }
}

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

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

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

十、總結(jié)

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

    這篇文章主要介紹了關(guān)于@Autowierd && @Resource的具體使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    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實現(xiàn)登錄驗證的方法示例

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

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

    SpringBoot項目中的視圖解析器問題(兩種)

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

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

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

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

    下面小編就為大家?guī)硪黄猄pringMVC通過注解獲得參數(shù)的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • Java使用HttpClient實現(xiàn)文件下載

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

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

最新評論