05.01 Fehler in Java
Nein, dieses Kapitel befasst sich nicht mit Bugs in der Programmiersprache Java, sondern mit Fehlermeldungen, Fehlerbehandlung und Fehlervermeidung in Ihren Programmen. Dabei wird grundsätzlich zwischen Compiler/Syntax-Fehlern und Runtime-Fehlern unterschieden.
Runtime-Fehler
Runtime-Fehler sind Fehler, die (aus welchen Gründen auch immer) während der Verwendung des Programms auftreten. Genau genommen wird dabei nochmal zwischen Exceptions (Unregelmäßigkeiten/Ausnahmen im Programmablauf), Errors (schwerwiegende Fehler) und einem VM-Crash (Bug in der Java-VM, einer nativen Bibliothek, …) unterschieden. Nachfolgend werden Ihnen die einzelnen Runtime-Fehler vorgestellt.
Exceptions (Ausnahmen)
Unter bestimmten Umständen kann die Verarbeitung eines Blocks schief laufen – z. B. weil eine Datei nicht gefunden, oder ein falscher Parameter übergeben wurde. In diesem Fall wird meistens eine Exception geworfen. Die Methode Integer.parseInt
, welche eine Zeichenkette in einen int
umwandelt, wirft z. B. eine Exception, wenn eine Zeichenkette übergeben wurde, die nicht in einen int
umgewandelt werden kann. Führen Sie folgenden Code aus:
public class RuntimeFehler { public static void main(String[] args) { Integer.parseInt("A"); } }
Natürlich kann ein „A“ nicht in eine Zahl umgewandelt werden. Deshalb wird eine NumberFormatException
geworfen – eine spezielle Exception, die immer dann geworfen wird, wenn mit dem Format einer Nummer etwas nicht in Ordnung ist.
Eine Exception ist nichts anderes als eine normale Klasse, die von
Exception
oder einer Unterklasse erbt. Exceptions werden für das jeweilige Problem (Datei nicht gefunden, fehlerhaftes Nummernformat, geteilt durch 0, …) spezialisiert.
Die Ausgabe auf der Konsole sieht dann in etwa so aus:
Exception in thread "main" java.lang.NumberFormatException: For input string: "A"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:63)
at java.lang.Integer.parseInt(Integer.java:481)
at java.lang.Integer.parseInt(Integer.java:531)
at RuntimeFehler.main(RuntimeFehler.java:3)
Die erste Zeile zeigt Ihnen die Fehlermeldung (java.lang.NumberFormatException
) gefolgt von einer detaillierteren Beschreibung (
For input string: "A"
). Anschließend folgt der so genannte StackTrace, die Nachverfolgung welche Klasse welche Methoden aufgerufen hat, dass es zu diesem Fehler kommen konnte. Es wird also ersichtlich, dass der eigentliche Fehler in der Methode NumberFormatException.forInputString
in der Zeile 63 ausgelöst wurde. Diese Methode erstellt eine neue NumberFormatException
. Geworfen wurde sie in Integer.parseInt
in der Zeile 481. Diese Methode wurde wiederum von der Methode Integer.parseInt
in der Zeile 531 aufgerufen
Hierbei handelt es sich um zwei gleich benannte Methoden mit unterschiedlichen Übergabeparameter, von denen die eine Methode die Andere aufruft.
, welche von unserer Klasse in der Methode RuntimeFehler.main
in der Zeile 3 aufgerufen wurde.
Zeilenangaben beziehen sich immer auf die jeweilige Klasse!
Um einen Fehler ausfindig zu machen, analysieren Sie am Besten den StackTrace von oben nach unten, bis Sie auf eine Klasse stoßen, die von Ihnen selbst programmiert wurde.
Exceptions gehören zu den „umgänglichsten Fehlermeldungen“ und können von Ihrem Programm abgefangen werden. Fangen Sie eine Exception allerdings nicht ab, wird Ihre Anwendung an dieser Stelle beendet. Mehr dazu im nächsten Kapitel.
Errors (Fehler)
Errors sind auch Unregelmäßigkeiten im Programmablauf, allerdings viel problematischer als eine Exception. Während eine Exception bei unerwarteten Situationen – bspw. einem fehlerhaften Argument – geworfen wird und der Programmierer dementsprechend darauf reagieren kann, so sind Errors wirklich schwerwiegende Fehler im Programmcode, die nicht so einfach abgefangen werden sollten/können.
Natürlich besteht die Möglichkeit Errors ähnlich wie Exceptions abzufangen. Dies sollte aber (laut Java-Doku) zum Einen nicht gemacht werden, und zum Anderen ist ein Error so schwerwiegend, dass auch ein Abfangen des Errors nicht zwingend dazu führt, dass das Programm weiterhin korrekt ausgeführt wird. Errors erben immer von der Klasse
Error
.
Ein solcher Fehler ist z. B. der OutOfMemoryError
, welcher immer dann geworfen wird, wenn der VM kein Speicher mehr zur Verfügung steht. Dies lässt sich auch relativ einfach simulieren. Führen Sie dazu folgenden Code aus:
public class RuntimeFehler { public static void main(String[] args) { StringBuilder build = new StringBuilder(); for (int i = 0; true; i++) { build.append(i); } } }
Hierbei wird solange eine Zahl an den StringBuilder
angefügt, bis der StringBuilder
so groß ist, dass er nicht mehr in den zur Verfügung stehenden Arbeitsspeicher passt. Infolge dessen wird ein OutOfMemoryError
geworfen, dessen Struktur Ihnen bereits aus den Fehlermeldungen der Exceptions bekannt ist.
Exception in thread "main" java.lang.OutOfMemoryError
at java.lang.StringBuilder.ensureCapacityImpl(StringBuilder.java:342)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at java.lang.StringBuilder.append(StringBuilder.java:172)
at RuntimeFehler.main(RuntimeFehler.java:6)
Beim Auftreten eines solchen Fehlers ist es zwingend notwendig die Fehlerursache ausfindig zu machen, das Problem zu untersuchen und letztendlich natürlich zu lösen.
VM-Crash
Als VM-Crash wird ein Absturz der Java Virtual-Machine bezeichnet. Dieser Fehler kann nicht abgefangen werden und hat die sofortige Beendigung des Programms zur Folge. Sie erkennen einen Absturz der VM an einer ähnlichen Konsolenausgabe wie dieser hier:
#
# An unexpected error has been detected by Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x7c911010, pid=2472, tid=2208
#
# Java VM: Java HotSpot(TM) Client VM (1.6.0_03-b05 mixed mode)
# Problematic frame:
# C [ntdll.dll+0x1010]
#
# An error report file with more information is saved as hs_err_pid2472.log
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
Zusätzlich wird immer im Ausführungsverzeichnis des Programms eine Log-Datei mit dem Namen hs_err_pidXXXX.log erstellt, die detaillierte Informationen zu diesem Fehler bereit hält.
Sollten bei Ihrem Programm ein solcher Fehler auftreten, versuchen Sie zuerst die Fehlerquelle und die nötigen Randbedingungen ausfindig zu machen und ihn zu reproduzieren. Dadurch steigert sich die Wahrscheinlichkeit enorm, diesen Fehler auszumerzen. Grundsätzlich kann ein VM-Crash mehrere Ursachen haben.
1.) Bug in einer externen Library
Können Sie den Crash auf eine externe Library zurückführen, so kontaktieren Sie am Besten den Entwickler dieser Lib mit einem detaillierten Fehlerbericht (log-Datei, Systeminformationen, Möglichkeiten zur Reproduzierung, …).
2.) Fehler in einer nativen Funktion
Wenn Sie selbst native Libraries (z. B. über JNI, siehe Kapitel 19. JNI) in Ihrem Programm verwenden, überprüfen Sie zuerst ob nicht evtl. ein Fehler in dieser nativen Library den VM-Crash verursacht hat. Eine native Library kann die VM sehr leicht ungewollt zum Absturz bringen.
3.) Bug in der VM
Sollte es sich wirklich um einen Fehler in der Java Virtual Machine handeln, hilft oftmals ein Update auf eine neuere Java-Version oder die Neuinstallation von Java. Wenn das nicht hilft, suchen Sie nach dem Fehler in der Java-Bug-Datenbank. Finden Sie Ihn dort nicht, können Sie einen neuen Bug bei Sun melden. Je nach Relevanz, Reproduzierbarkeit und detaillierter Beschreibung des Bugs wird Ihr Anliegen mehr oder weniger schnell bearbeitet.
Ich habe bis jetzt zwei Bugs gefunden und an Sun übermittelt. Der Erste wurde innerhalb weniger Tage gefixt, auf die Lösung des Zweiten warte ich bis heute noch.
4.) …
Hilft alles nichts, bleibt nur die detaillierte Analyse der Log-Datei – ggf. von einem Experten oder in einer entsprechenden Community.
Compiler/Syntax-Fehler
Ein Compiler/Syntax-Fehler ist ein Fehler, der den Compiler daran hindert, Byte-Code aus Ihrem Source-Code zu erstellen. Dies kann passieren, wenn Sie z. B. am Ende einer Anweisung ein Semikolon vergessen haben, eine Methode aufrufen, die nicht existiert, oder eine Klasse nicht importiert haben.
Dabei wird beim Kompilieren eine Fehlermeldung ausgegeben, die der Programmierer richtig deuten und anschließend ausbessern muss. Da das JDK aber relativ aussagekräftige Fehlermeldungen ausgibt, ist es größtenteils glücklicherweise nicht sonderlich schwer den Fehler ausfindig zu machen.
Beim Kompilieren dieser Klasse:
public class Syntax { public static void main(String[] args) { System.out.println("Hallo Welt") } }
wird Ihnen der Compiler eine Fehlermeldung wie diese hier zeigen:
Syntax.java:3: ';' expected
System.out.println("Hallo Welt")
^
1 error
Dieser Fehler ist leicht zu deuten. In der Datei Syntax.java in der Zeile 3 wird ein „;“ erwartet (Übersetzung der 1. Zeile). Und zwar am Ende der Zeile System.out.println("Hallo Welt")
, da dort der kleine Pfeil hindeutet.
Compiler-/Syntax-Fehler müssen folglich zwingend behoben werden, bevor Sie Ihr Programm kompilieren können.