Kiedy powinniśmy wywołać System.exit w Javie

193

W Javie, jaka jest różnica z System.exit(0)następującym kodem lub bez ?

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? 
    }      
}

Dokument mówi: „Ta metoda nie zwraca normalnie.” Co to znaczy?

pierrotlefou
źródło

Odpowiedzi:

207

System.exit()może być użyty do uruchomienia haków zamykających przed zamknięciem programu. Jest to wygodny sposób radzenia sobie z zamykaniem w większych programach, w których wszystkie części programu nie mogą (i nie powinny) być świadome siebie. Następnie, jeśli ktoś chce odejść, może po prostu zadzwonić System.exit(), a haki zamykania (jeśli są odpowiednio skonfigurowane) zajmują się wszystkimi niezbędnymi ceremoniami wyłączania, takimi jak zamykanie plików, zwalnianie zasobów itp.

„Ta metoda nigdy nie powraca normalnie”. oznacza tylko, że metoda nie zwróci; gdy nić trafi tam, nie wróci.

Innym, może bardziej powszechnym sposobem na wyjście z programu jest po prostu dotarcie do końca mainmetody. Ale jeśli uruchomione są wątki niebędące demonami, nie zostaną one zamknięte, a zatem maszyna JVM nie zostanie zamknięta. Tak więc, jeśli masz takie wątki inne niż demony, potrzebujesz innych środków (oprócz haków zamykania), aby zamknąć wszystkie wątki inne niż demony i zwolnić inne zasoby. Jeśli nie ma innych wątków niebędących demonami, powrót z mainspowoduje zamknięcie JVM i wywołanie haków zamykania.

Z jakiegoś powodu haki zamykające wydają się być niedocenianym i źle zrozumianym mechanizmem, a ludzie wymyślają na nowo koło z wszelkiego rodzaju własnymi niestandardowymi hackami, aby wyjść z programów. Zachęcam do używania haków zamykających; to wszystko jest w standardowym środowisku uruchomieniowym , którego i tak będziesz używać.

Joonas Pulakka
źródło
10
„Ta metoda nigdy nie powraca normalnie”. oznacza tylko, że metoda nie zwróci; gdy nić trafi tam, nie wróci. W szczególności należy zauważyć, że oznacza to, że nie można przetestować metody, która wywołuje wywołanie System.exit (0) ...
Bill Michell,
5
-1 Niepoprawnie. Haki zamykania będą działać, jeśli JVM zakończy się normalnie, bez względu na to, czy jest to spowodowane przez System.exit czy zakończenie main (). Zobacz zx81 / doku / java / javadoc / j2se1.5.0 / docs / api / java / lang /…
sleske 15.09.10
31
@sleske: Zakończenie main () nie jest wystarczające, jeśli istnieją inne wątki nie-demonowe. Wyłączanie jest inicjowane dopiero po zakończeniu ostatniego wątku nie-demona, chyba że jawnie wywołasz System.exit (). Jest to wyraźnie określone w dokumentacji środowiska wykonawczego.
Joonas Pulakka
3
Zauważ, że jeśli hak zamykający z kolei opiera się na wątku o nazwie System.exit, nastąpi impas.
djechlin
8
Coś do dodania, jeśli ktoś zatrzyma środowisko uruchomieniowe, haki zamykające nie będą działać ( Runtime.getRuntime().halt())
Łotr
50

W takim przypadku nie jest to konieczne. Żadne dodatkowe wątki nie zostaną uruchomione, nie zmieniasz kodu wyjścia (domyślnie 0) - w zasadzie nie ma sensu.

Kiedy dokumentacja mówi, że metoda nigdy nie zwraca normalnie, oznacza to, że kolejny wiersz kodu jest faktycznie nieosiągalny, nawet jeśli kompilator nie wie, że:

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

Zostanie zgłoszony wyjątek lub maszyna wirtualna zakończy działanie przed zwróceniem. Nigdy nie „po prostu wróci”.

Bardzo rzadko warto dzwonić na System.exit()IME. Może to mieć sens, jeśli piszesz narzędzie wiersza polecenia i chcesz wskazać błąd za pomocą kodu wyjścia, a nie tylko zgłaszać wyjątek ... ale nie pamiętam, kiedy ostatni raz użyłem go w normalnym kodzie produkcyjnym .

Jon Skeet
źródło
2
Dlaczego kompilator nie wie o tym? Czy System.exit () nie jest wystarczająco specjalny, aby zagwarantować określony kod wykrywania?
Bart van Heukelom,
3
@ Bart: Nie, nie sądzę. Umieszczenie specjalnych przypadków w języku dla takich rzeczy zwiększa złożoność języka z bardzo niewielką korzyścią.
Jon Skeet
Myślałem, że „nigdy nie wraca normalnie” miało związek z „nagłym ukończeniem” oświadczenia. (JLS sekcja 14.1). Czy się mylę?
aioobe,
@aioobe: Nie, masz rację. System.exit()nigdy nie zakończy się normalnie - zawsze albo zakończy się z wyjątkiem, albo zamknie maszynę wirtualną (która tak naprawdę nie jest objęta JLS).
Jon Skeet,
1
@piechuckerr: Co sprawia, że ​​tak myślisz? Jest całkowicie uzasadnione, aby wywołać go z wartością inną niż 0, aby wskazać powłoce wywołującej (lub cokolwiek innego), że program napotkał błąd.
Jon Skeet
15

Metoda nigdy nie zwraca, ponieważ jest to koniec świata i żaden z Twoich kodów nie zostanie wykonany w następnej kolejności.

Twoja aplikacja w twoim przykładzie i tak by się zakończyła w tym samym miejscu w kodzie, ale jeśli użyjesz System.exit. masz możliwość zwrócenia niestandardowego kodu do środowiska, na przykład powiedzmy

System.exit(42);

Kto skorzysta z Twojego kodu wyjścia? Skrypt, który wywołał aplikację. Działa w systemach Windows, Unix i wszystkich innych środowiskach skryptowych.

Po co zwracać kod? Aby powiedzieć rzeczy takie jak „nie udało mi się”, „Baza danych nie odpowiedziała”.

Aby zobaczyć, jak uzyskać wartość kodu wyjścia i użyć go w skrypcie powłoki unix lub skrypcie cmd systemu Windows, możesz sprawdzić tę odpowiedź na tej stronie

mico
źródło
14

System.exit(0)kończy JVM. W takich prostych przykładach trudno dostrzec różnicę. Parametr jest przekazywany z powrotem do systemu operacyjnego i zwykle jest używany do wskazania nieprawidłowego zakończenia (np. Jakiś błąd krytyczny), więc jeśli wywołałeś java z pliku wsadowego lub skryptu powłoki, możesz uzyskać tę wartość i uzyskać pomysł jeśli aplikacja zakończyła się powodzeniem.

Miałoby to duży wpływ, jeśli System.exit(0)wywołałeś aplikację wdrożoną na serwerze aplikacji (pomyśl o tym przed wypróbowaniem).

Qwerky
źródło
12

W aplikacjach, które mogą mieć skomplikowane haki zamykania, tej metody nie należy wywoływać z nieznanego wątku. System.exitnigdy nie kończy normalnie, ponieważ wywołanie zostanie zablokowane, dopóki JVM nie zostanie zakończone. To tak, jakby działający kod miał wyciągniętą wtyczkę przed zakończeniem. Wywołanie System.exitspowoduje zainicjowanie haków zamykania programu, a każdy wątek, który System.exitwywoła, będzie blokowany aż do zakończenia programu. Ma to implikację, że jeśli hak zamykający z kolei prześle zadanie do wątku, z którego System.exitzostał wywołany, program zablokuje się.

Zajmuję się tym w moim kodzie w następujący sposób:

public static void exit(final int status) {
    new Thread("App-exit") {
        @Override
        public void run() {
            System.exit(status);
        }
    }.start();
}
djechlin
źródło
Miałem również problem polegający na tym, że System.exit tak naprawdę nie zakończyłby JVM. Ale dzieje się tak tylko pod pewnymi warunkami. Zrobiony przeze mnie wątek nie dał mi żadnych informacji na temat tego, które wątki / programy obsługi zamykania blokują zamknięcie. Korzystanie z kodu opisanego w komentarzu pomogło mi go rozwiązać!
Kai
7

Chociaż odpowiedź była naprawdę pomocna, ale niektórzy pominęli jakieś dodatkowe szczegóły. Mam nadzieję, że poniżej pomoże zrozumieć proces zamykania w java, oprócz powyższej odpowiedzi:

  1. W uporządkowanym * zamykaniu JVM najpierw uruchamia wszystkie zarejestrowane haki zamykania. Haki zamykania to nierozpoczęte wątki zarejestrowane w Runtime.addShutdownHook.
  2. JVM nie udziela żadnych gwarancji co do kolejności uruchamiania haków zamykających. Jeśli jakieś wątki aplikacji (demon lub nondaemon) nadal działają podczas zamykania, kontynuują działanie jednocześnie z procesem zamykania .
  3. Po zakończeniu wszystkich haków zamykania JVM może zdecydować się na uruchomienie finalizatorów, jeśli runFinalizersOnExit ma wartość true, a następnie zatrzymuje się.
  4. JVM nie próbuje zatrzymać ani przerwać wątków aplikacji, które nadal działają w czasie zamykania; zostają nagle zakończone, gdy JVM w końcu zatrzymuje się.
  5. Jeśli haki zamykania lub finalizatory nie zostaną zakończone, wówczas proces uporządkowanego wyłączania „zawiesza się” i JVM musi zostać nagle zamknięty.
  6. W przypadku nagłego zamknięcia JVM nie musi wykonywać żadnych czynności poza zatrzymaniem JVM; haki zamykające nie będą działać.

PS: JVM może zostać zamknięty w sposób uporządkowany lub nagły .

  1. Uporządkowane zamykanie jest inicjowane, gdy ostatni „normalny” (nondaemon) wątek zostanie zakończony, ktoś wywoła System.exit lub za pomocą innych środków specyficznych dla platformy (takich jak wysłanie SIGINT lub naciśnięcie Ctrl-C).
  2. Chociaż powyższy jest standardowym i preferowanym sposobem zamykania JVM, można go również gwałtownie zamknąć, wywołując Runtime.halt lub zabijając proces JVM za pośrednictwem systemu operacyjnego (np. Wysyłając SIGKILL).
Jan
źródło
7

NIGDY nie należy dzwonić System.exit(0)z następujących powodów:

  1. Jest to ukryte „goto”, a „gotos” przerywają przepływ sterowania. Poleganie na hakach w tym kontekście jest mentalnym mapowaniem, o którym każdy programista w zespole musi wiedzieć.
  2. Wyjście z programu „normalnie” zapewnia ten sam kod wyjścia dla systemu operacyjnego, ponieważ System.exit(0)jest nadmiarowy.

    Jeśli twój program nie może wyjść „normalnie”, straciłeś kontrolę nad swoim rozwojem [projekt]. Zawsze powinieneś mieć pełną kontrolę nad stanem systemu.

  3. Problemy z programowaniem, takie jak uruchamianie wątków, które nie są zatrzymywane, zwykle zostają ukryte.
  4. Możesz napotkać niespójny stan aplikacji przerywający wątki nieprawidłowo. (Patrz # 3)

Nawiasem mówiąc: Zwracanie innych kodów powrotu niż 0 ma sens, jeśli chcesz wskazać nieprawidłowe zakończenie programu.

oopexpert
źródło
2
„Zawsze powinnaś mieć pełną kontrolę nad stanem systemu”. W Javie jest to po prostu niemożliwe. Frameworki IoC i serwery aplikacji to tylko 2 bardzo popularne i szeroko stosowane sposoby rezygnacji z kontroli stanu systemu. I to w porządku.
mhlz
Uruchom System.exit (0) na swoim serwerze aplikacji i powiedz mi o reakcjach administratora twojego serwera ... Pominąłeś temat tego pytania i mojej odpowiedzi.
oopexpert
Może nie byłem wystarczająco jasny. Zawsze powinieneś mieć pełną kontrolę nad stanem systemu, za który jesteś odpowiedzialny. Tak, niektóre obowiązki zostały przeniesione na serwery aplikacji i IoC. Ale nie mówiłem o tym.
oopexpert
5

Potrzebny jest System.exit

  • gdy chcesz zwrócić kod błędu inny niż 0
  • gdy chcesz wyjść z programu z miejsca, które nie jest main ()

W twoim przypadku robi dokładnie to samo, co zwykły powrót z głównego.

MFF
źródło
Co masz na myśli z tym ostatnim? Właściwym sposobem na zamknięcie programu jest zatrzymanie wszystkich wątków (ładnie), ruch, który nie musi być inicjowany z głównego wątku, więc nie exitjest to twoja jedyna opcja.
Bart van Heukelom,
@ Bart: to, co opisujesz, to jeden z możliwych sposobów zamknięcia programu, a nie właściwy sposób . Nie ma nic złego w korzystaniu z haków zamykających, aby ładnie zatrzymać wszystkie wątki. I uruchamiane są haki zamykające exit. Nie jedyna opcja, ale zdecydowanie opcja warta rozważenia.
Joonas Pulakka
Ale to, co zbieram z dokumentów, haki zamykające są bardzo delikatne i mogą nie mieć dużo czasu na robienie tego, co chcesz. W każdym razie użyj ich, aby dobrze zamknąć w przypadku zamknięcia systemu operacyjnego lub coś takiego, ale jeśli masz zamiar samodzielnie wywołać System.exit (), myślę, że lepiej jest zrobić kod zamykający przed nim (po czym prawdopodobnie nie zrobisz tego potrzebujesz System.exit () już)
Bart van Heukelom
1
Haki zamykające mają cały czas potrzebny na zakończenie. Maszyna wirtualna nie jest zatrzymywana przed powrotem wszystkich haków. Rzeczywiście można zapobiec zamknięciu maszyny wirtualnej, rejestrując hak zamykania, którego wykonanie zajmuje bardzo dużo czasu. Ale oczywiście możliwe jest wykonanie sekwencji wyłączania na różne, alternatywne sposoby.
Joonas Pulakka
Powrót z main nie zostanie zamknięty, jeśli istnieją inne wątki niebędące demonami.
djechlin
4

Mówi to specyfikacja języka Java

Wyjdź z programu

Program kończy całą swoją aktywność i kończy działanie, gdy wydarzy się jedna z dwóch rzeczy:

Wszystkie wątki, które nie są wątkami demonów, kończą się.

Niektóre wątki wywołują metodę wyjścia klasy Runtime lub klasy System , a operacja wyjścia nie jest zabroniona przez menedżera bezpieczeństwa.

Oznacza to, że powinieneś go używać, gdy masz duży program (cóż, przynajmniej większy od tego) i chcesz zakończyć jego wykonywanie.

Marcin Szymczak
źródło
3

Jeśli w JVM działa inny program i korzystasz z System.exit, ten drugi program również zostanie zamknięty. Wyobraź sobie na przykład, że uruchamiasz zadanie Java w węźle klastra i że program Java, który zarządza węzłem klastra, działa w tej samej maszynie JVM. Jeśli zadanie użyje System.exit, nie tylko zamknie zadanie, ale także „zamknie cały węzeł”. Nie można wysłać kolejnego zadania do tego węzła klastra, ponieważ program zarządzania został przypadkowo zamknięty.

Dlatego nie używaj System.exit, jeśli chcesz mieć możliwość kontrolowania swojego programu z innego programu Java w tej samej JVM.

Użyj System.exit, jeśli chcesz celowo zamknąć pełną JVM i jeśli chcesz skorzystać z możliwości opisanych w innych odpowiedziach (np. Haki zamykania: hak zamykania Java , niezerowa wartość zwracana dla wiersza poleceń wywołania: jak uzyskać status wyjścia programu Java w pliku wsadowym Windows ).

Zobacz także wyjątki Runtime: System.exit (num) lub wyrzucić RuntimeException z main?

Stefan
źródło