Maison > Java > javaDidacticiel > le corps du texte

Comment résoudre le problème selon lequel Java System#exit ne peut pas quitter le programme

WBOY
Libérer: 2023-04-25 09:55:06
avant
2001 Les gens l'ont consulté

Contexte

Un ami a rencontré une situation : java.lang.System#exit ne peut pas quitter l'application.

J'ai été surpris lorsque j'ai entendu cette situation. Cette fonction peut-elle toujours fonctionner ? Cela me rend curieux

Ensuite, mon ami a continué à donner sa description de la scène : lorsque l'application Dubbo se connecte au centre d'enregistrement, si la connexion (timeout) échoue, elle est censée appeler System#exit pour quitter l'application, mais le le programme n'appuie pas sur Attendre pour quitter, le processus JVM existe toujours

En même temps, si vous mettez le code qui exécute System#exit dans un autre thread, le programme peut se terminer comme prévu et le processus JVM se termine

Le le pseudo code est décrit comme suit :

Future<Object> future = 连接注册中心的Future;
try {
    Object o = future.get(3, TimeUnit.SECONDS);
} catch (Exception e) {
	log.error("connect failed xxxx");
    System.exit(1); // 程序无法退出
}

-----------

Future<Object> future = 连接注册中心的Future;
try {
    Object o = future.get(3, TimeUnit.SECONDS);
} catch (Exception e) {
	log.error("connect failed xxxx");
    new Thread(() -> System.exit(1)).start(); // 程序能按期望退出
}
Copier après la connexion

Ami Le scénario auquel nous sommes confrontés est beaucoup plus complexe que celui décrit par le pseudocode, mais les problèmes essentiels auxquels nous sommes confrontés sont les mêmes.

Une question plus générale, dans le constructeur org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry de Dubbo, exécutez directement System.exit(1); et le programme ne peut pas se terminer de manière asynchrone. le fil de discussion peut se terminer comme prévuSystem.exit(1);程序无法退出,放在异步线程中执行却可以按期望退出

即:

// org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry

public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    super(url);
    System.exit(1); //JVM进程无法退出
    // ...(省略)
}

-----------
// org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry

public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    super(url);
    new Thread(() -> {System.exit(1);}).start(); //JVM进程正常退出
    // ...(省略)
}
Copier après la connexion

这就更令人惊奇了!

问题排查

要找出问题产生的原因,首先得有一些预备知识,否则会茫然无措,感觉无从下手

  • java.lang.System#exit 方法是Java提供的能够停止JVM进程的方法

  • 该方法被触发时,JVM会去调用Shutdown Hook(关闭勾子)方法,直到所有勾子方法执行完毕,才会关闭JVM进程

由上述第2点猜测:是否存在死循环的勾子函数无法退出,以致JVM没有去关闭进程?

举个例子:

public static void main(String[] args) {
    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        while (true) {
            try {
                System.out.println("closing...");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
        }
    }));

    System.out.println("before exit...");
    System.exit(0); 
    System.out.println("after exit..."); //代码不会执行
}
Copier après la connexion

如上,在main方法里先注册了一个shutdown hook,该勾子函数是个死循环,永远也不会退出,每3秒打印一次"closing…"

接着执行System.exit(0);方法,期望退出JVM进程

before exit...
closing...
closing...
closing...
closing...
closing...

...

结果是控制台不断打印"closing…",且JVM进程没有退出

原因正是上述第二点储备知识提到的:JVM会等待所有勾子执行完毕之后,才关闭进程。而示例中的shutdown hook 永远也不会执行完毕,因此JVM进程也不会被关闭

尽管有了储备知识,仍然很疑惑:如果存在死循环的shutdown hook,那么System.exit无论是在主线程中调用,还是在异步线程中调用,都应该不会关闭JVM进程;反之,如果不存在死循环的shutdown hook,无论是在哪个线程调用,都应该关闭JVM进程。为什么在背景的伪代码中,却是因为不同的调用线程执行System.exit,导致不一样的结果呢?

这时候只好想办法,看看shutdown hook们都在偷摸干啥事,为什么未执行完毕,以致JVM进程不能退出

恰好对Dubbo的源码也略有研究,很容易就找到org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry的构造函数,并在其中加上一行代码,如下所示,改完之后重新编译源码,并引入自己的工程中进行Debug

注:本次使用的Dubbo版本为2.7.6

// org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    super(url);
    System.exit(1); // 新增加的一行代码
    // ...(省略)
}
Copier après la connexion

启动工程,熟悉Dubbo的朋友应该会知道,应用启动的过程中会去注册中心(这儿是Zookeeper)注册或者订阅,因为启动的是消费者,因此应用会尝试连接注册中心Zookeeper,会走到ZookeeperRegistry的构造函数,由于构造函数第二行是新增的代码System.exit(1);按照背景的说法,JVM不会退出,且会卡死,这时候,借助IDEA的"快照"功能,可以"拍"下Java线程栈的运行情况,功能上相当于执行jstack命令

Comment résoudre le problème selon lequel Java System#exit ne peut pas quitter le programme

Comment résoudre le problème selon lequel Java System#exit ne peut pas quitter le programme

从线程栈中看出一个可疑的线程:DubboShutdownHook

从名字上可以看出是一个Dubbo注册的一个shutdown hook,其主要目的是为了关闭连接、做一些资源的回收等工作

从图中也可以看出,线程阻塞在org.apache.dubbo.registry.support.AbstractRegistryFactory

C'est-à-dire :

public static void destroyAll() {
    if (!destroyed.compareAndSet(false, true)) {
        return;
    }

    if (LOGGER.isInfoEnabled()) {
        LOGGER.info("Close all registries " + getRegistries());
    }
    // Lock up the registry shutdown process
    LOCK.lock(); // 83行,DubboShutdownHook线程阻塞在此处
    try {
        for (Registry registry : getRegistries()) {
            try {
                registry.destroy();
            } catch (Throwable e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
        REGISTRIES.clear();
    } finally {
        // Release the lock
        LOCK.unlock();
    }
}
Copier après la connexion

C'est encore plus incroyable !

Dépannage

Pour découvrir la cause du problème, vous devez d'abord avoir quelques connaissances préparatoires, sinon vous serez perdu et vous vous sentirez incapable de démarrer🎜
  • 🎜java.lang La méthode .System#exit est une méthode fournie par Java qui peut arrêter le processus JVM🎜
  • 🎜Lorsque cette méthode est déclenchée, la JVM appellera la méthode Shutdown Hook jusqu'à ce que toutes les méthodes hook soient exécuté, le processus JVM sera-t-il arrêté🎜
🎜Devinez à partir du point 2 ci-dessus : 🎜Y a-t-il une fonction de crochet de boucle infinie qui ne peut pas se terminer, afin que la JVM n'arrête pas le processus ? 🎜🎜🎜🎜Par exemple : 🎜🎜
org.apache.dubbo.registry.support.AbstractRegistryFactory#getRegistry(org.apache.dubbo.common.URL)
Copier après la connexion
🎜Comme ci-dessus, un hook d'arrêt est enregistré dans la méthode principale. Cette fonction de hook est une boucle infinie et elle n'imprimera jamais "closing..." toutes les 3 secondes🎜🎜. exécutez la méthode System.exit(0);, en espérant quitter le processus JVM🎜
🎜avant de quitter...
fermeture...
fermeture...
fermeture...
fermeture...
fermeture...🎜🎜...🎜
🎜Le résultat est que la console continue d'imprimer "fermeture…" le processus ne se termine pas🎜 🎜La raison est exactement ce qui est mentionné dans le deuxième point de connaissance de réserve ci-dessus : la JVM attendra que tous les hooks soient exécutés avant de fermer le processus. Le hook d'arrêt dans l'exemple ne sera jamais exécuté, donc le processus JVM ne sera pas arrêté🎜🎜Malgré les connaissances accumulées, je suis toujours confus : s'il existe un hook d'arrêt en boucle infinie, alors System.exit</ code>Qu'il soit appelé dans le thread principal ou dans un thread asynchrone, il ne devrait 🎜pas🎜 arrêter le processus JVM, à l'inverse, s'il n'y a pas de hook d'arrêt de boucle infinie, quel que soit le thread appelé, il devrait 🎜le fera 🎜 Fermez le processus JVM. Pourquoi dans le pseudocode de 🎜Background🎜, différents threads appelants exécutent <code>System.exit, ce qui donne des résultats différents ? 🎜🎜À ce moment-là, j'ai dû réfléchir à un moyen de voir ce que faisaient secrètement les hooks d'arrêt, et pourquoi ils n'étaient pas terminés et que le processus JVM ne pouvait pas se terminer 🎜🎜J'ai fait quelques recherches sur le code source. de Dubbo, et j'ai trouvé facilement org.apache. Le constructeur de dubbo.registry.zookeeper.ZookeeperRegistry#ZookeeperRegistry, et ajoutez-y une ligne de code, comme indiqué ci-dessous. Après la modification, recompilez le code source et introduisez-le dans le vôtre. projet de débogage🎜
🎜Remarque : Cette utilisation La version de Dubbo est 2.7.6🎜
// org.apache.dubbo.registry.support.AbstractRegistryFactory

public Registry getRegistry(URL url) {
    // ...(省略)
    LOCK.lock(); // 获取锁
    try {
        // ...(省略)
        // 创建Registry,由于我们选用的注册中心是Zookeeper,因此通过SPI选择了ZookeeperRegistryFactory对ZookeeperRegistry进行创建,最终会调用到我们添加过一行System.exit的ZookeeperRegistry构造函数中
        
        registry = createRegistry(url); 
        
        // ...(省略)
    } finally {
        // Release the lock
        LOCK.unlock(); // 创建完registry,与注册中心连上之后,才会释放锁
    }
}
Copier après la connexion
Copier après la connexion
🎜 Démarrez le projet Les amis qui connaissent Dubbo doivent savoir que pendant le processus de démarrage de l'application, cela fonctionnera. au centre d'inscription (ici Zookeeper) pour s'inscrire ou s'abonner, car c'est le consommateur qui est démarré. Par conséquent, l'application va tenter de se connecter au centre d'inscription Zookeeper et ira au constructeur de ZookeeperRegistry</code. >, car la deuxième ligne du constructeur est le code nouvellement ajouté <code>System.exit(1); selon 🎜Background🎜 dit que la JVM ne se fermera pas et restera bloquée à ce moment-là, avec. à l'aide de la fonction "instantané" d'IDEA, vous pouvez "photographier" l'état d'exécution de la pile de threads Java, ce qui est fonctionnellement équivalent à l'exécution de jstack</ code>Command🎜🎜<img src="https://img .php.cn/upload/article/000/887/227/168238771440471.png" alt="Comment résoudre le problème selon lequel Java System#exit ne peut pas quitter le programme" / >🎜🎜<img src="https:// img.php.cn/upload/article/000/887/227/168238771474546.jpg" alt="Comment résoudre le problème selon lequel Java System#exit ne peut pas quitter le programme" />🎜 🎜Un fil suspect peut être vu depuis le pile de threads : 🎜DubboShutdownHook🎜🎜🎜Comme le montre le nom, il s'agit d'un hook d'arrêt enregistré par Dubbo. Son objectif principal est de fermer la connexion et d'effectuer un recyclage des ressources 🎜🎜De Cela peut également être vu sur l'image. que le fil est bloqué à la ligne 83 de <code>org.apache.dubbo.registry.support.AbstractRegistryFactory🎜
// org.apache.dubbo.registry.zookeeper.ZookeeperRegistryFactory

public Registry createRegistry(URL url) {
	// 调用修改过源码的ZookeeperRegistry构造函数
    return new ZookeeperRegistry(url, zookeeperTransporter);
}
Copier après la connexion
Copier après la connexion
🎜Il est évident d'après le code que le verrou ne peut pas être obtenu, donc le fil est bloqué à la ligne 83, en attente d'acquisition du verrou. En d'autres termes, il existe d'autres threads détenant le verrou, mais il n'a pas encore été libéré. ​​DubboShutdownHook doit attendre🎜🎜Utilisez IDEA pour vérifier où le verrou a été acquis, comme indiqué ci-dessous. tu trouves 🎜
// java.lang.System

public static void exit(int status) {
    Runtime.getRuntime().exit(status);
}
Copier après la connexion
Copier après la connexion
🎜, tu auras le cadenas🎜
// org.apache.dubbo.registry.support.AbstractRegistryFactory

public Registry getRegistry(URL url) {
    // ...(省略)
    LOCK.lock(); // 获取锁
    try {
        // ...(省略)
        // 创建Registry,由于我们选用的注册中心是Zookeeper,因此通过SPI选择了ZookeeperRegistryFactory对ZookeeperRegistry进行创建,最终会调用到我们添加过一行System.exit的ZookeeperRegistry构造函数中
        
        registry = createRegistry(url); 
        
        // ...(省略)
    } finally {
        // Release the lock
        LOCK.unlock(); // 创建完registry,与注册中心连上之后,才会释放锁
    }
}
Copier après la connexion
Copier après la connexion
// org.apache.dubbo.registry.zookeeper.ZookeeperRegistryFactory

public Registry createRegistry(URL url) {
	// 调用修改过源码的ZookeeperRegistry构造函数
    return new ZookeeperRegistry(url, zookeeperTransporter);
}
Copier après la connexion
Copier après la connexion

如此,System.exit无法退出JVM进程的问题总算真相大白了:

1.Dubbo启动过程中会先获取锁,然后创建registry与注册中心进行连接,在ZookeeperRegistry中调用了java.lang.System#exit方法,程序转而执行"唤起shutdown hook"的代码并阻塞等待所有勾子函数执行完毕,而此时,之前持有的锁并没有释放

2.所有勾子函数(每个勾子函数都对应一个线程)被唤醒并执行,其中有一个Dubbo的勾子函数在执行的过程中,需要获取步骤1中的锁,由于获取锁失败,就阻塞等待着

3.由于1没有释放锁的情况下等待2执行完,而2的执行需要等待1释放锁,这样就形成了一个类似"死锁"的场景,因此也就导致了程序卡死,而JVM进程还存活的现象。之所以称为"类似"死锁,是因为1中执行System.exit的线程,也即持有锁的线程,永远不会走到释放锁的代码:一旦程序进入System.exit的世界里,就像进了一个单向虫洞,只能进不能出,如果勾子函数执行完毕,JVM进程接着就会被关闭,不会有机会再释放锁

那么,为什么在异步线程中执行System.exit,却能够正常退出JVM?

那是因为:"唤起shutdown hook"并阻塞等待所有勾子函数执行完毕的线程是其它线程(此处假设是线程A),该线程在阻塞时并未持有任何锁,而主线程会继续往下执行并接着释放锁。一旦锁释放,Shutdown hook就有机会持有该锁,并且执行其它资源的回收操作,等到所有的shutdown hook执行完毕,A线程就能从阻塞中返回并执行halt方法关闭JVM,因此能够正常退出JVM进程

深入学习

以上是对java.lang.System#exit 无法退出程序问题的分析,来龙去脉已经阐述清楚,受益于对Dubbo源码的了解以及正确的排查思路和排查手段,整个问题排查过程其实并没有花太多时间,但可以趁着这个机会,把java.lang.System#exit系统学习一下,或许会对以后问题排查、基础组件设计提供一些思路

System#exit

// java.lang.System

public static void exit(int status) {
    Runtime.getRuntime().exit(status);
}
Copier après la connexion
Copier après la connexion

Terminates the currently running Java Virtual Machine. The argument serves as a status code; by convention, a nonzero status code indicates abnormal termination.
This method calls the exit method in class Runtime. This method never returns normally.
The call System.exit(n) is effectively equivalent to the call:
Runtime.getRuntime().exit(n)

这个方法实现非常简单,是Runtime#exit的一个简便写法,其作用是用来关闭JVM进程,一旦调用该方法,永远也不会从该方法正常返回:执行完该方法后JVM进程就直接关闭了。

入参status取值分两类:0值与非0值,0值意味着正常关闭,非0值意味着异常关闭。

传入0值[有可能]会去执行所有的finalizer方法,非0值则一定不会执行(都不正常了,还执行啥finalizer呢?)。这儿提及[有可能]是因为,默认并不会执行finalizers,需要调用java.lang.Runtime#runFinalizersOnExit方法开启,而该方法早被JDK标识为Deprecated,因此通常情况下是不会开启的

// java.lang.Runtime

@Deprecated
public static void runFinalizersOnExit(boolean value) {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        try {
            security.checkExit(0);
        } catch (SecurityException e) {
            throw new SecurityException("runFinalizersOnExit");
        }
    }
    Shutdown.setRunFinalizersOnExit(value);
}
Copier après la connexion

接着看java.lang.Runtime#exit,可以看到,最终调用的是Shutdown.exit(status);,该方法是个包级别可见的方法,外部不可见

// java.lang.Runtime

public void exit(int status) {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkExit(status);
    }
    Shutdown.exit(status);
}
Copier après la connexion
// java.lang.Shutdown

static void exit(int status) {
    // ...(省略)
    synchronized (Shutdown.class) {
        /* Synchronize on the class object, causing any other thread
         * that attempts to initiate shutdown to stall indefinitely
         */
        // 执行shutdown序列
        sequence();
        // 关闭JVM
        halt(status);
    }
}
Copier après la connexion
// java.lang.Shutdown

private static void sequence() {
    // ...(省略)
    runHooks();
    // ...(省略)
}
Copier après la connexion
// java.lang.Shutdown

private static void runHooks() {
    for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
        try {
            Runnable hook;
            synchronized (lock) {
                // 这个锁很重要,目的是通过Happens-Before保证内存的可见性
                currentRunningHook = i;
                hook = hooks[i];
            }
            if (hook != null) hook.run(); //执行勾子函数
        } catch(Throwable t) {
            if (t instanceof ThreadDeath) {
                ThreadDeath td = (ThreadDeath)t;
                throw td;
            }
        }
    }
}
Copier après la connexion

java.lang.Shutdown#runHooks有两个点需要注意,第一点MAX_SYSTEM_HOOKS(hooks)这个并不是我们注册的shutdown hooks,而是按顺序预定义的系统关闭勾子,目前JDK源码(JDK8)预定义了三个:

  • Console restore hook

  • Application hooks

  • DeleteOnExit hook

其中,Application hooks才是我们应用程序中主动注册的shutdown hook。

java.lang.ApplicationShutdownHooks类初始化时,会执行static代码块,并在其中注册了Application hooks

// java.lang.ApplicationShutdownHooks

class ApplicationShutdownHooks {
    /* The set of registered hooks */
    // 这个才是我们应用程序代码中注册的shutdown hook
    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;
        }
    }
Copier après la connexion

其次要注意的点是,给hook变量赋值的时候进行了加锁

Runnable hook;
synchronized (lock) {
    currentRunningHook = i;
    hook = hooks[i];
}
Copier après la connexion

一般而言,给局部变量赋值是不需要加锁的,因为局部变量是栈上变量,而线程栈之间数据是隔离的,不会出现线程安全的问题,因此不需要靠加锁来保证数据并发访问的安全性。

而此处加锁也并非为了解决线程安全问题,其真正的目的在于,通过Happens-Before规则来保证hooks的内存可见性:An unlock on a monitor happens-before every subsequent lock on that monitor。

如果不加锁,有可能导致从hooks数组中读取到的值并不是内存中最新的变量值,而是一个旧值

上面是读取hooks数组给hook变量赋值,为了满足HB(Happens-Before)原则,需要确保写操作中同样对hooks变量进行了加锁,因此我们看一下写hooks数组的地方,如下:

// java.lang.Shutdown

static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
    synchronized (lock) {
    		// ...(省略)
        hooks[slot] = hook;
    }
}
Copier après la connexion

操作确实加了锁,这样才能让接下来的 操作的加锁行为满足HB原则

由于篇幅原因,就不展开具体的HB介绍,相信了解过HB原则的朋友一下就能明白其中的原理

这个点个人感觉很有意思,因为锁的作用不单是为了保证线程安全,还可以用来做为内存通信、保证内存可见性的手段,因此可以当作面试的一个点,当下次面试官问到:你写的代码中用过锁(synchronized)吗?什么场景用到锁?都集群部署了,单机锁还有意义吗? 我们就可以回答:为了保证内存的可见性,balabalaba

所以你瞧,这个点其实也给我们设计基础组件带来很大的启发,synchronized在当今集群、分布式环境下并非一无是处,总有合适的地方在等待着它发挥光和热

注:JDK源码中真处处是宝藏,很多地方隐藏着巧妙而不可缺少的设计

在给hook变量赋值之后,就执行 if (hook != null) hook.run();,其中会执行到Application hooks,即上面提到的在ApplicationShutdownHooks类初始化时注册的勾子,勾子内部调用了java.lang.ApplicationShutdownHooks#runHooks方法

// java.lang.ApplicationShutdownHooks

Shutdown.add(1 /* shutdown hook invocation order */,
    false /* not registered if shutdown in progress */,
    new Runnable() {
        public void run() {
            runHooks();
        }
    }
);
Copier après la connexion
// java.lang.ApplicationShutdownHooks

static void runHooks() {
    Collection<Thread> threads;
    synchronized(ApplicationShutdownHooks.class) {
        threads = hooks.keySet(); // hooks才是应用程序真正注册的shutdown hook
        hooks = null;
    }
		// 每一个shutdown hook都对应一个thread,由此可见是并发执行关闭勾子函数
    for (Thread hook : threads) {
        hook.start();
    }
    for (Thread hook : threads) {
        while (true) {
            try {
                hook.join(); // 死等到hook执行完毕
                break;
            } catch (InterruptedException ignored) {
                // 即便被唤醒都不搭理,接着进行下一轮循环,继续死等
            }
        }
    }
}
Copier après la connexion

上面的hooks才是应用程序真正注册的shutdown hook,由源码可以看出,每一个hook都对应着一个thread,且调用了它们的start方法,即开启thread,意味着shutdown hook是并发无序地执行

接着,唤起shutdown hook的线程,会通过死循环和join死等到所有关闭勾子都执行完毕,且忽略任何 唤醒异常。也即是说,如果勾子们不执行完,唤醒线程是不会离开的

等所有的Application hooks执行完毕,接下来会执行DeleteOnExit hook(如果存在),等所有system hooks执行完毕,也基本意味着sequence方法执行完毕,接下来就执行halt方法关闭JVM虚拟机

synchronized (Shutdown.class) {
    sequence();
    halt(status);
}
Copier après la connexion

这里额外还有一个知识点,上文只是提了一嘴,可能会容易忽略,此处拿出来解释一下:执行java.lang.System#exit永远也不会从该方法正常返回,也即是说,即便System#exit后边跟着的是finally,也不会执行 。一不注意就容易掉坑里

try {
    // ...
    System.exit(0);
} finally {
    // 这里的代码永远执行不到
}
Copier après la connexion

java.lang.Runtime#addShutdownHook

聊完System#exit方法,接着来聊聊注册shutdown hook的方法。该方法本身实现上很简单,如下示:

// java.lang.Runtime
public void addShutdownHook(Thread hook) {
    // ...(省略)
    ApplicationShutdownHooks.add(hook);
}

// java.lang.ApplicationShutdownHooks
static synchronized void add(Thread hook) {
    // ...(省略)
    hooks.put(hook, hook);
}
Copier après la connexion

需要注意的是,注册的关闭勾子会在以下几种时机被调用到

程序正常退出

  • 最后一个非守护线程执行完毕退出时

  • System.exit方法被调用时

程序响应外部事件

  • 程序响应用户输入事件,例如在控制台按ctrl+c(^+c)

  • 程序响应系统事件,如用户注销、系统关机等

除此之外,shutdown hook是不会被执行的

Shutdown hook存在的意义之一,是能够帮助我们实现优雅停机,而优雅停机的意义是:应用的重启、停机等操作,不影响业务的连续性

以Dubbo Provider的视角为例,优雅停机需要满足两点基本诉求:

  • Consumer不应该请求到已经下线的Provider

  • 在途请求需要处理完毕,不能被停机指令中断

Dubbo注册了Shutdown hook,JVM在收到操作系统发来的关闭指令时,会执行关闭勾子

  • 在勾子中停止与注册中心的连接,注册中心会通知Consumer某个Provider已下线,后续不应该再调用该Provider进行服务。此行为是断掉上游流量,满足第一点诉求

  • 接着,勾子执行Protocol(Dubbo相关概念)的注销逻辑,在其中判断server(Dubbo相关概念)是否还在处理请求,在超时时间内等待所有任务处理完毕,则关闭server。此行为是处理在途请求,满足第二点述求

因此,一种优雅停机的整体方案如下:

$pid = ps | grep xxx // 查找要关闭的应用
kill $pid // 发出关闭应用指令
sleep for a period of time // 等待一段时间,让应用程序执行shutdown hook进行现场的保留跟资源的清理工作

$pid = ps | grep xxx // 再次查找要关闭的应用,如果还存在,就需要强行关闭应用
if($pid){kill -9 $pid} // 等待一段时间之后,应用程序仍然没有正常停止,则需要强行关闭应用
Copier après la connexion

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:yisu.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal