1. Deadlock beim Lesen/Schreiben von Streams
Einer der unangenehmsten Fehler – wenn ein Prozess hängen bleibt und nicht beendet wird, obwohl scheinbar alles richtig gemacht wurde. Häufige Ursache – Überlauf des Ausgabepuffers oder Fehler des externen Prozesses. Wenn man nicht beide Streams (stdout und stderr) liest, kann der Prozess beim Versuch, etwas auszugeben, „blockieren“, weil niemand die Daten aus seinem Puffer abholt.
Beispiel für das Problem
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Wir lesen nur stdout und ignorieren stderr!
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
Wenn der Befehl etwas nach stderr schreibt (zum Beispiel schreibt java -version fast immer dorthin!), läuft dieser Stream über und der Prozess hängt.
So geht’s richtig
Lesen Sie beide Streams parallel (über separate Threads oder ExecutorService):
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// stdout lesen
Thread stdoutThread = new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("[stdout] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
// stderr lesen
Thread stderrThread = new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.err.println("[stderr] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
stdoutThread.start();
stderrThread.start();
process.waitFor();
stdoutThread.join();
stderrThread.join();
Fazit:
Wenn Sie stderr nicht lesen – kann der Prozess hängen bleiben.
Wenn Sie stdout nicht lesen – kann er ebenfalls hängen bleiben.
Lesen Sie beide Streams – dann sind Sie auf der sicheren Seite!
2. Probleme mit der Zeichenkodierung
Externe Prozesse können eine andere Zeichenkodierung für die Textausgabe verwenden als Ihr Java‑Programm standardmäßig. Wenn Sie die richtige Kodierung nicht angeben, erhalten Sie „Kauderwelsch“ statt Text (besonders bei Kyrillisch sichtbar).
Fehlerbeispiel
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
Dieser Code verwendet die systemweite Standardkodierung. Wenn der externe Prozess z. B. in UTF-8 schreibt, bei Ihnen aber Windows-1251 aktiv ist, wird Kyrillisch zu Kauderwelsch.
So geht’s richtig
Geben Sie die benötigte Kodierung explizit an, wenn Sie sie kennen:
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
);
Falls Sie unsicher sind – sehen Sie in der Programmdokumentation nach oder probieren Sie mehrere Varianten aus.
Tipp: Unter Windows verwenden Konsolen-Utilities häufig CP866 oder Windows-1251, unter Linux – UTF-8.
3. Plattformunterschiede
Befehle, die auf einem Betriebssystem funktionieren, können auf einem anderen fehlen. Zum Beispiel gibt es ls unter Linux/Mac, aber nicht unter Windows (dort – dir). Syntax der Befehle, Pfadtrennzeichen und Anführungszeichen unterscheiden sich.
Fehlerbeispiel
ProcessBuilder builder = new ProcessBuilder("ls", "-l");
builder.start(); // Unter Windows: "ls" nicht gefunden!
So geht’s richtig
String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder builder;
if (os.contains("win")) {
builder = new ProcessBuilder("cmd", "/c", "dir");
} else {
builder = new ProcessBuilder("ls", "-l");
}
Dateipfade: verwenden Sie File.separator anstelle von „/“ oder „\“, um keine Probleme mit Pfaden zu bekommen.
4. Probleme mit Berechtigungen
Einige Befehle erfordern Administratorrechte (Löschen von Systemdateien, Ändern der Netzwerkeinstellungen usw.). Bei unzureichenden Rechten wird der Befehl mit Fehler beendet oder gar nicht gestartet.
Beispiel
ProcessBuilder builder = new ProcessBuilder("rm", "-rf", "/root/secret.txt");
Process process = builder.start();
// ... wir erwarten den Fehler Permission denied
So geht’s richtig
- Prüfen Sie, ob Ihr Befehl erhöhte Rechte benötigt.
- Verarbeiten Sie den Rückgabecode des Prozesses über process.exitValue().
- Lesen Sie stderr – dort steht meist die Fehlerursache (z. B. „Permission denied“).
5. Ressourcenlecks
Wenn Sie die Streams des Prozesses (InputStream, OutputStream, ErrorStream) nicht schließen, können sie „hängen“, Ressourcen belegen und sogar das Beenden blockieren. Ebenso kann der Prozess selbst zu einem „Zombie“ im System werden.
Fehlerbeispiel
Process process = builder.start();
// ... wir arbeiten, schließen die Streams aber nicht!
So geht’s richtig
Verwenden Sie try-with-resources für Streams:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
// Ausgabe lesen
}
Beenden Sie den Prozess nach getaner Arbeit ordnungsgemäß:
process.destroy(); // Prozess beenden (falls er noch läuft)
Achtung: Wenn Sie Streams nicht schließen, sind Speicherlecks, Hänger und Systemprobleme möglich (insbesondere bei massenhaft gestarteten Prozessen).
6. Deadlock bei interaktiver Kommunikation
Bei interaktivem Austausch gerät man leicht in eine gegenseitige Blockade: Java wartet auf eine Antwort, der externe Prozess auf Ihre Daten. Am Ende „schweigen“ beide. Oder Sie haben eine Nachricht gesendet, lesen die Antwort aber nicht – irgendwann läuft der Puffer der externen Anwendung über und sie stellt das Schreiben ein.
Um das zu vermeiden, trennen Sie die Verantwortlichkeiten: Ein Thread ist fürs Lesen zuständig, ein anderer fürs Schreiben. So läuft die Interaktion parallel und niemand blockiert den anderen. Lassen Sie nach dem Abschluss außerdem keine offenen Streams zurück – schließen Sie sie, damit das System keine unnötigen Ressourcen belegt.
GO TO FULL VERSION