Quand devrions-nous appeler System.exit en Java

193

En Java, quelle est la différence avec ou sans System.exit(0)dans le code suivant?

public class TestExit
{      
    public static void main(String[] args)
    { 
        System.out.println("hello world");

        System.exit(0);  // is it necessary? And when it must be called? 
    }      
}

Le document dit: "Cette méthode ne revient jamais normalement." Qu'est-ce que ça veut dire?

pierrotlefou
la source

Réponses:

207

System.exit()peut être utilisé pour exécuter des hooks d'arrêt avant la fermeture du programme. C'est un moyen pratique de gérer l'arrêt des programmes plus volumineux, où toutes les parties du programme ne peuvent pas (et ne devraient pas) se connaître les unes les autres. Ensuite, si quelqu'un veut quitter, il peut simplement appeler System.exit(), et les hooks d'arrêt (s'ils sont correctement configurés) se chargent de faire toutes les cérémonies d'arrêt nécessaires telles que la fermeture de fichiers, la libération de ressources, etc.

"Cette méthode ne revient jamais normalement." signifie simplement que la méthode ne reviendra pas; une fois qu'un fil y va, il ne reviendra pas.

Une autre façon, peut-être plus courante, de quitter un programme consiste simplement à atteindre la fin de la mainméthode. Mais si des threads non démon sont en cours d'exécution, ils ne seront pas arrêtés et la JVM ne se fermera donc pas. Ainsi, si vous avez de tels threads non démon, vous avez besoin d'un autre moyen (que les hooks d'arrêt) pour arrêter tous les threads non démon et libérer d'autres ressources. S'il n'y a pas d'autres threads non démon, le retour de mainfermera la JVM et appellera les hooks d'arrêt.

Pour une raison quelconque, les crochets d'arrêt semblent être un mécanisme sous-évalué et mal compris, et les gens réinventent la roue avec toutes sortes de hacks personnalisés propriétaires pour quitter leurs programmes. J'encourage l'utilisation de crochets d'arrêt; tout est là dans le Runtime standard que vous utiliserez de toute façon.

Joonas Pulakka
la source
10
"Cette méthode ne revient jamais normalement." signifie simplement que la méthode ne reviendra pas; une fois qu'un fil y va, il ne reviendra pas. Notez en particulier que cela signifie que vous ne pouvez pas tester à l'unité une méthode qui effectue un appel System.exit (0) ...
Bill Michell
5
-1 Incorrect. Les hooks d'arrêt s'exécuteront si la JVM se termine normalement, que ce soit à cause de System.exit ou de la fin de main (). Voir zx81 / doku / java / javadoc / j2se1.5.0 / docs / api / java / lang /…
sleske
31
@sleske: La terminaison de main () n'est pas suffisante s'il y a d'autres threads non démons autour. L'arrêt n'est lancé qu'après la fin du dernier thread non démon, sauf si vous appelez explicitement System.exit (). Cela est clairement indiqué dans la documentation Runtime.
Joonas Pulakka
3
Notez que si votre hook d'arrêt repose à son tour sur le thread qui a appelé System.exit, vous bloquerez.
djechlin
8
Quelque chose à ajouter, si quelqu'un arrête un runtime, les hooks d'arrêt ne fonctionneront pas ( Runtime.getRuntime().halt())
Rogue
50

Dans ce cas, ce n'est pas nécessaire. Aucun thread supplémentaire n'aura été démarré, vous ne modifiez pas le code de sortie (qui est par défaut 0) - en gros, c'est inutile.

Lorsque les documents indiquent que la méthode ne retourne jamais normalement, cela signifie que la ligne de code suivante est effectivement inaccessible, même si le compilateur ne sait pas que:

System.exit(0);
System.out.println("This line will never be reached");

Soit une exception sera levée, soit la VM s'arrêtera avant de revenir. Il ne "reviendra jamais".

Il est très rare de mériter d'appeler System.exit()IME. Cela peut être logique si vous écrivez un outil de ligne de commande et que vous souhaitez indiquer une erreur via le code de sortie plutôt que de simplement lancer une exception ... mais je ne me souviens pas de la dernière fois que je l'ai utilisé dans le code de production normal .

Jon Skeet
la source
2
Pourquoi le compilateur ne le sait-il pas? System.exit () n'est-il pas assez spécial pour justifier un code de détection spécifique?
Bart van Heukelom
3
@Bart: Non, je ne pense pas. Mettre des cas spéciaux dans la langue pour des choses comme cela ajoute à la complexité de la langue avec très peu d'avantages.
Jon Skeet
Je pensais que «ne revient jamais normalement» avait à voir avec «l'achèvement brutal» de la déclaration. (Section JLS 14.1). Ai-je tort?
aioobe
@aioobe: Non, tu as raison. System.exit()ne se terminera jamais normalement - il se terminera toujours soit avec une exception, soit avec l'arrêt de la machine virtuelle (ce qui n'est pas vraiment couvert dans le JLS).
Jon Skeet
1
@piechuckerr: Qu'est-ce qui vous fait penser cela? Il est tout à fait raisonnable de l'appeler avec une valeur autre que 0, pour indiquer au shell appelant (ou autre) que le programme a rencontré une erreur.
Jon Skeet
15

La méthode ne revient jamais car c'est la fin du monde et aucun de votre code ne sera exécuté ensuite.

Votre application, dans votre exemple, quitterait quand même au même endroit dans le code, mais, si vous utilisez System.exit. vous avez la possibilité de retourner un code personnalisé à l'environnement, comme, par exemple,

System.exit(42);

Qui va utiliser votre code de sortie? Un script qui a appelé l'application. Fonctionne sous Windows, Unix et tous les autres environnements scriptables.

Pourquoi retourner un code? Pour dire des choses comme "Je n'ai pas réussi", "La base de données n'a pas répondu".

Pour voir comment obtenir la valeur d'un code de sortie et l'utiliser dans un script shell unix ou un script cmd windows, vous pouvez vérifier cette réponse sur ce site

mico
la source
14

System.exit(0)met fin à la JVM. Dans des exemples simples comme celui-ci, il est difficile de percevoir la différence. Le paramètre est renvoyé à l'OS et est normalement utilisé pour indiquer une fin anormale (par exemple une sorte d'erreur fatale), donc si vous appelez java à partir d'un fichier batch ou d'un script shell, vous pourrez obtenir cette valeur et vous faire une idée si la demande a réussi.

Cela aurait un impact considérable si vous appeliez System.exit(0)une application déployée sur un serveur d'applications (pensez-y avant de l'essayer).

Qwerky
la source
12

Dans les applications qui peuvent avoir des hooks d'arrêt complexes, cette méthode ne doit pas être appelée à partir d'un thread inconnu. System.exitne se termine jamais normalement car l'appel sera bloqué jusqu'à la fin de la JVM. C'est comme si le code en cours d'exécution avait la prise d'alimentation tirée avant de pouvoir terminer. L'appel System.exitlancera les hooks d'arrêt du programme et le thread qui System.exitbloquera les appels jusqu'à la fin du programme. Cela implique que si le hook d'arrêt soumet à son tour une tâche au thread à partir duquel il a System.exitété appelé, le programme se bloquera.

Je gère cela dans mon code avec les éléments suivants:

public static void exit(final int status) {
    new Thread("App-exit") {
        @Override
        public void run() {
            System.exit(status);
        }
    }.start();
}
Djechlin
la source
J'ai également eu le problème où System.exit ne mettrait pas réellement fin à la JVM. Mais cela ne s'est produit que dans certaines conditions. Un threadump que j'ai pris ne m'a pas donné d'informations sur les threads / gestionnaires d'arrêt qui bloquent l'arrêt. L'utilisation du code décrit dans le commentaire m'a aidé à le résoudre!
Kai
7

Bien que la réponse ait été vraiment utile, mais certains ont manqué de détails supplémentaires. J'espère que ci-dessous aidera à comprendre le processus d'arrêt en java, en plus de la réponse ci-dessus:

  1. Lors d'un arrêt ordonné *, la JVM démarre d'abord tous les hooks d'arrêt enregistrés. Les hooks d'arrêt sont des threads non démarrés qui sont enregistrés avec Runtime.addShutdownHook.
  2. JVM ne donne aucune garantie sur l'ordre de démarrage des hooks d'arrêt. Si des threads d'application (démon ou nondaemon) sont toujours en cours d'exécution au moment de l'arrêt, ils continuent de s'exécuter simultanément avec le processus d'arrêt .
  3. Une fois tous les hooks d'arrêt terminés, la JVM peut choisir d'exécuter les finaliseurs si runFinalizersOnExit est vrai, puis s'arrête.
  4. JVM n'essaie pas d'arrêter ou d'interrompre les threads d'application toujours en cours d'exécution au moment de l'arrêt; ils s'arrêtent brusquement lorsque la JVM s'arrête.
  5. Si les crochets d'arrêt ou les finaliseurs ne se terminent pas, le processus d'arrêt ordonné «se bloque» et la machine virtuelle Java doit être arrêtée brusquement.
  6. Lors d'un arrêt brutal, la JVM n'est pas tenue de faire autre chose que d'arrêter la JVM; les crochets d'arrêt ne fonctionneront pas.

PS: La JVM peut s'arrêter de manière ordonnée ou abrupte .

  1. Un arrêt ordonné est lancé lorsque le dernier thread «normal» (nondaemon) se termine, que quelqu'un appelle System.exit ou par d'autres moyens spécifiques à la plate-forme (tels que l'envoi d'un SIGINT ou la frappe de Ctrl-C).
  2. Bien que ci-dessus soit la manière standard et préférée pour la JVM de s'arrêter, elle peut également être arrêtée brutalement en appelant Runtime.halt ou en tuant le processus JVM via le système d'exploitation (comme l'envoi d'un SIGKILL).
John
la source
7

Il ne faut JAMAIS appeler System.exit(0)pour ces raisons:

  1. C'est un "goto" caché et des "gotos" qui cassent le flux de contrôle. S'appuyer sur les crochets dans ce contexte est une cartographie mentale dont chaque développeur de l'équipe doit être conscient.
  2. Quitter le programme "normalement" fournit le même code de sortie au système d'exploitation car System.exit(0)il est donc redondant.

    Si votre programme ne peut pas quitter "normalement", vous avez perdu le contrôle de votre développement [conception]. Vous devez toujours avoir un contrôle total sur l'état du système.

  3. Les problèmes de programmation tels que l'exécution de threads qui ne sont pas arrêtés sont normalement masqués.
  4. Vous pouvez rencontrer un état d'application incohérent interrompant anormalement les threads. (Voir # 3)

Soit dit en passant: le retour d'autres codes de retour que 0 est logique si vous souhaitez indiquer la fin anormale d'un programme.

oopexpert
la source
2
"Vous devriez toujours avoir un contrôle total sur l'état du système." Ce n'est tout simplement pas possible en Java. Les frameworks IoC et les serveurs d'applications ne sont que 2 façons très populaires et largement utilisées pour abandonner votre contrôle de l'état du système. Et c'est tout à fait bien.
mhlz
Veuillez exécuter System.exit (0) dans votre serveur d'applications et faites-moi part des réactions de l'administrateur de votre serveur ... Vous avez manqué le sujet de cette question et ma réponse.
oopexpert
Peut-être que je n'étais pas assez clair. Vous devriez toujours avoir un contrôle total sur l'état du système dont vous êtes responsable. Oui, certaines responsabilités ont été transférées aux serveurs d'applications et à l'IoC. Mais je ne parlais pas de ça.
oopexpert
5

System.exit est nécessaire

  • lorsque vous souhaitez renvoyer un code d'erreur différent de 0
  • lorsque vous voulez quitter votre programme d'un endroit qui n'est pas principal ()

Dans votre cas, il fait exactement la même chose que le simple retour de main.

mfx
la source
Que voulez-vous dire avec ce dernier? La bonne façon d'arrêter le programme est d'arrêter tous les threads (gentiment), un mouvement qui n'a pas à être initié à partir du thread principal, donc ce n'est pas comme si c'était exitvotre seule option.
Bart van Heukelom
@Bart: ce que vous décrivez est l' un des moyens possibles pour arrêter un programme, pas la bonne . Il n'y a rien de mal à utiliser des crochets d'arrêt pour arrêter correctement tous les threads. Et les hooks d'arrêt sont lancés avec exit. Pas la seule option, mais certainement une option à considérer.
Joonas Pulakka
Mais ce que je comprends des documents, les crochets d'arrêt sont très délicats et peuvent ne pas avoir beaucoup de temps pour faire ce que vous voulez qu'ils fassent. Par tous les moyens, utilisez-les pour quitter correctement en cas d'arrêt du système d'exploitation ou quelque chose, mais si vous allez appeler System.exit () vous-même, je pense qu'il vaut mieux faire le code d'arrêt avant lui (après quoi vous ne serez probablement pas besoin de System.exit () plus)
Bart van Heukelom
1
Les crochets d'arrêt ont tout le temps nécessaire pour terminer. La machine virtuelle n'est pas arrêtée avant que tous les crochets ne soient revenus. Il est en effet possible d'empêcher la machine virtuelle de se fermer en enregistrant un hook d'arrêt qui prend très longtemps à s'exécuter. Mais bien sûr, il est possible de faire la séquence d'arrêt de différentes manières différentes.
Joonas Pulakka
Le retour de main ne s'arrêtera pas s'il existe d'autres threads non démon.
djechlin
4

La spécification du langage Java dit que

Quitter le programme

Un programme met fin à toutes ses activités et se termine lorsque deux choses se produisent:

Tous les threads qui ne sont pas des threads démon se terminent.

Certains threads invoquent la méthode de sortie de la classe Runtime ou de la classe System , et l'opération de sortie n'est pas interdite par le gestionnaire de sécurité.

Cela signifie que vous devez l'utiliser lorsque vous avez un gros programme (enfin, au moins plus gros que celui-ci) et que vous souhaitez terminer son exécution.

Marcin Szymczak
la source
3

Si vous avez un autre programme en cours d'exécution dans la JVM et que vous utilisez System.exit, ce deuxième programme sera également fermé. Imaginez par exemple que vous exécutez un travail java sur un nœud de cluster et que le programme java qui gère le nœud de cluster s'exécute dans la même JVM. Si le travail utilisait System.exit, non seulement il quitterait le travail, mais également "arrêterait le nœud complet". Vous ne pourriez pas envoyer un autre travail à ce nœud de cluster car le programme de gestion a été fermé accidentellement.

Par conséquent, n'utilisez pas System.exit si vous souhaitez pouvoir contrôler votre programme à partir d'un autre programme java au sein de la même machine virtuelle Java.

Utilisez System.exit si vous souhaitez fermer la JVM complète à dessein et si vous souhaitez profiter des possibilités qui ont été décrites dans les autres réponses (par exemple, arrêter les hooks: Java shutdown hook , valeur de retour non nulle pour la ligne de commande) appels: comment obtenir l'état de sortie d'un programme Java dans un fichier batch Windows ).

Jetez également un œil à Exceptions Runtime: System.exit (num) ou lancer une RuntimeException à partir de main?

Stefan
la source