1. Deadlock przy odczycie/zapisie strumieni
Jednym z najbardziej dokuczliwych błędów jest sytuacja, gdy proces się zawiesza i nie kończy, choć wydaje się, że wszystko zrobiono poprawnie. Częsty powód — przepełnienie bufora wyjścia albo błędy procesu zewnętrznego. Jeśli nie czytać obu strumieni (stdout i stderr), proces może się „zablokować” przy próbie wypisania czegokolwiek, ponieważ nikt nie odbiera danych z jego bufora.
Przykład problemu
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Czytamy tylko stdout, a stderr ignorujemy!
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
Jeśli polecenie zapisuje coś do stderr (np. java -version prawie zawsze tam pisze!), ten strumień się przepełni i proces się zawiesi.
Jak zrobić to poprawnie
Czytaj oba strumienie równolegle (za pomocą oddzielnych wątków lub ExecutorService):
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Czytamy stdout
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();
}
});
// Czytamy stderr
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();
Wniosek:
Jeśli nie czytać stderr — proces może się zawiesić.
Jeśli nie czytać stdout — również może się zawiesić.
Czytaj oba strumienie — i kłopot zniknie!
2. Problemy z kodowaniem znaków
Procesy zewnętrzne mogą używać innego kodowania tekstu niż Twoja aplikacja Java domyślnie. Jeśli nie podasz właściwego kodowania, dostaniesz „krzaczki” zamiast tekstu (szczególnie widoczne przy cyrylicy).
Przykład błędu
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
Ten kod używa systemowego kodowania domyślnego. Jeśli jednak proces zewnętrzny wypisuje np. w UTF-8, a u Ciebie jest Windows-1251, cyrylica zamieni się w „krzaczki”.
Jak zrobić to poprawnie
Przekazuj jawnie właściwe kodowanie, jeśli je znasz:
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
);
Jeśli nie masz pewności — zajrzyj do dokumentacji programu albo spróbuj kilku wariantów.
Wskazówka: w Windows narzędzia konsolowe często używają CP866 lub Windows-1251, a w Linuksie — UTF-8.
3. Różnice między platformami
Polecenia działające w jednym systemie operacyjnym mogą nie istnieć w innym. Na przykład ls jest w Linux/Mac, ale nie w Windows (tam — dir). Różni się składnia poleceń, separatory ścieżek i cudzysłowy.
Przykład błędu
ProcessBuilder builder = new ProcessBuilder("ls", "-l");
builder.start(); // W Windows: "ls" nie znaleziono!
Jak zrobić to poprawnie
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");
}
Ścieżki do plików: używaj File.separator zamiast „/” lub „\”, aby nie wpaść w kłopoty ze ścieżkami.
4. Problemy z uprawnieniami
Niektóre polecenia wymagają uprawnień administratora (usuwanie plików systemowych, zmiana ustawień sieci itp.). Przy braku uprawnień polecenie zakończy się błędem albo w ogóle się nie uruchomi.
Przykład
ProcessBuilder builder = new ProcessBuilder("rm", "-rf", "/root/secret.txt");
Process process = builder.start();
// ... oczekujemy błędu Permission denied
Jak zrobić to poprawnie
- Sprawdzaj, czy Twoje polecenie wymaga podwyższonych uprawnień.
- Obsługuj kod wyjścia procesu przez process.exitValue().
- Czytaj stderr — tam najczęściej jest przyczyna błędu (np. „Permission denied”).
5. Wycieki zasobów
Jeśli nie zamykać strumieni procesu (InputStream, OutputStream, ErrorStream), mogą „wisieć”, zajmować zasoby, a nawet blokować zakończenie. Podobnie, jeśli nie zakończyć samego procesu, może stać się „zombie” w systemie.
Przykład błędu
Process process = builder.start();
// ... pracujemy, ale nie zamykamy strumieni!
Jak zrobić to poprawnie
Używaj try-with-resources dla strumieni:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
// Czytamy wyjście
}
Po zakończeniu pracy z procesem poprawnie go zatrzymuj:
process.destroy(); // Zakończ proces (jeśli wciąż żyje)
Uwaga: jeśli nie zamkniesz strumieni — możliwe są wycieki pamięci, zawieszenia i problemy systemowe (zwłaszcza przy masowym uruchamianiu procesów).
6. Deadlock przy interaktywnej komunikacji
Przy interaktywnej wymianie danych łatwo trafić na zakleszczenie: Java czeka na odpowiedź, a proces zewnętrzny — na Twoje dane. W efekcie obie strony „milczą”. Albo wysłałeś komunikat, ale nie czytasz odpowiedzi — w pewnym momencie programowi zewnętrznemu przepełni się bufor i przestanie pisać.
Aby tego uniknąć, rozdzielaj odpowiedzialności: jeden wątek odpowiada za odczyt, drugi — za zapis. Dzięki temu komunikacja przebiega równolegle i nikt nikogo nie blokuje. Nie zostawiaj też otwartych strumieni po zakończeniu — zamknij je, aby system nie trzymał zbędnych zasobów.
GO TO FULL VERSION