Wie behandelt die JVM eine von der Hauptmethode ausgelöste Ausnahme?

10

Ich verstehe Ausnahmen, wirf sie aus, behandle sie und propagiere sie an eine Methode weiter unten im Aufrufstapel (dh throws).

Was ich nicht verstehe ist folgendes:

public static void main(String[] args) throws Exception {
    ...
}

Nun gehe ich davon aus, dass in dem Fall, in dem mainein Exceptionausgelöst wird, die JVM damit umgeht (richtig?). Wenn das der Fall ist, lautet meine Frage:

Wie geht die JVM mit Ausnahmen um, die von ausgelöst werden main? Was tut es?

Aviv Cohn
quelle

Antworten:

19

Sie könnten denken, dass die public static void mainMethode in Java oder die mainFunktion in C der eigentliche Einstiegspunkt Ihres Programms ist - aber das ist es nicht. Alle Hochsprachen (einschließlich C) haben eine Sprachlaufzeit, die das Programm initialisiert und dann den Kontrollfluss an den Einstiegspunkt überträgt. Im Fall von Java umfasst die Initialisierung:

  • Einrichten der JVM
  • Laden der erforderlichen Klassen
  • Ausführen statischer Initialisierungsblöcke. Dies kann benutzerdefinierten Code ausführen, bevor er mainaufgerufen wird. Diese Blöcke sollen keine Ausnahmen auslösen.

Es gibt verschiedene Möglichkeiten, die Ausnahmebehandlung zu implementieren. Für die Zwecke dieser Frage können sie jedoch alle als Black Box betrachtet werden. Wichtig ist jedoch, dass die Sprachlaufzeit immer einen äußersten Ausnahmebehandler bereitstellen muss, der alle Ausnahmen abfängt, die nicht vom Benutzercode erfasst werden. Dieser Ausnahmebehandler druckt normalerweise eine Stapelverfolgung aus, fährt das Programm ordnungsgemäß herunter und beendet es mit einem Fehlercode. Das ordnungsgemäße Herunterfahren des Programms umfasst das Zerstören des Objektdiagramms, das Aufrufen von Finalisierern und das Freigeben von Ressourcen wie Speicher, Dateihandles oder Netzwerkverbindungen.

Zur Veranschaulichung können Sie sich die Laufzeit vorstellen, in der der gesamte Code in einem riesigen Try-Catch verpackt ist, der aussieht

try {
    loadClasses();
    runInitializers();
    main(argv);
    System.exit(0);
} catch (Throwable e) {
    e.printStackTrace();
    System.exit(-1);
}

außer dass es nicht notwendig ist, dass eine Sprache tatsächlich Code wie diesen ausführt. Dieselbe Semantik kann im Code für throw(oder einem gleichwertigen Code ) implementiert werden , der nach dem ersten anwendbaren Ausnahmebehandler sucht.

amon
quelle
9

Der gesamte Java-Code wird im Kontext eines Threads ausgeführt . Das verknüpfte JavaDoc erklärt die Fehlerbehandlungs- und Beendigungskriterien, aber hier ist der Kern davon:

  • Die JVM dreht sich selbst und bereitet die Ausführungsumgebung vor.
  • Die JVM erstellt einen Thread, der die main()Methode unter Verwendung der anwendbaren Befehlszeilenparameter ausführt.
  • Die JVM legt einen Standard-Handler für nicht erfasste Ausnahmen fest, der die Ausnahme als Standardfehler druckt und beendet.
  • Die JVM führt den Thread aus.

Im Falle einer nicht erfassten Ausnahme stirbt das Programm effektiv gemäß dem oben genannten dritten Punkt. Dieses Verhalten wird in der Java-Sprachspezifikation, Abschnitt 11.3, näher spezifiziert


zusätzliche Information

Andere haben statische Blöcke erwähnt und wie sie zuvor ausgeführt wurden main(). Dies erfordert jedoch etwas mehr Erklärung, um richtig zu verstehen.

Beim Laden einer Klasse muss der Klassenlader alle static finalStatus initialisieren und alle staticBlöcke ausführen , bevor die Klasse verwendet werden kann, um instanziierende Instanzen der Klasse einzuschließen (abgesehen davon: Erstellen Sie eine Java-Klasse, in der eine Klassenkonstante in einem statischen Block nach dem Erstellen eines initialisiert wird Instanz der Klasse, und der Konstruktor verweist auf die Konstante. Boom!). Dies alles geschieht jedoch in der Klassenladelogik, bevor ein Code auf die Klasse verweisen kann . Darüber hinaus wird die Klasse in den Thread geladen, auf den die Klasse verwiesen hat.

Dies bedeutet, dass wenn die Klasse, die main()Referenzen enthält, auf eine andere Klasse verweist (z. B. Klassenkonstante), diese Klasse vor der Ausführung geladen werden mussmain() , um ihre statischen Blöcke einzuschließen. Andernfalls werden die statischen Blöcke wie oben ausgeführt. Wenn die Klasse nicht geladen werden kann, kann auch die enthaltende Klasse main()nicht geladen werden und das Programm wird beendet.

Ein weiteres FYI: statische Blöcke können werfen. Errorswerden wie sie sind geworfen. Exceptionssind verboten (Kompilierungsfehler). RuntimeExceptionssind in ExceptionInInitializerError eingeschlossen . Diese werden gemäß dem nicht erfassten Ausnahmebehandler behandelt, der normalerweise entweder den Thread oder die Anwendung (Hauptthread) beendet, es sei denn, Sie wickeln die Klassenreferenz (und das Laden) sorgfältig in ein try- ein catch.

Benni
quelle