1. Czym są procesy w systemie operacyjnym i po co uruchamiać je z Javy
Procesy: od JVM do bash i z powrotem
Kiedy włączasz komputer i uruchamiasz przeglądarkę, komunikator lub grę — to wszystko są oddzielne procesy. System operacyjny uruchamia dla każdego programu własny „mini-świat”: przydziela pamięć, czas procesora, zezwala na pracę z plikami i siecią. Dzięki temu programy działają obok siebie i sobie nie przeszkadzają.
Program w Javie nie jest wyjątkiem. Gdy uruchamiasz java MyApp, system tworzy dla niego osobny proces ze wszystkimi potrzebnymi zasobami. W jego wnętrzu działa już twój program: liczy, rysuje, czyta pliki — robi wszystko, co do niego należy.
Ale czasem sama Java nie wystarcza. Bywa, że trzeba poprosić o pomoc inny program — uruchomić archiwizer, aby spakować pliki, wywołać ffmpeg, aby przetworzyć wideo, albo po prostu sprawdzić, jaka wersja Javy jest zainstalowana na komputerze. To właśnie uruchamianie procesu zewnętrznego: Java mówi systemowi „uruchom mi to narzędzie”, a następnie odbiera wynik jego działania.
W istocie to sposób, by uczynić program bardziej elastycznym: połączyć różne narzędzia, zautomatyzować rutynę albo wpleść własną logikę w istniejące procesy systemowe. Niekiedy łatwiej zlecić część pracy zewnętrznemu poleceniu, niż wyważać otwarte drzwi w kodzie.
JVM vs. proces zewnętrzny
Proces JVM — twój program wykonywany w maszynie wirtualnej Javy.
Proces zewnętrzny — dowolny inny program: kalkulator, skrypt Pythona, wiersz poleceń, a nawet inna instancja Javy.
2. Klasa ProcessBuilder
W „zamierzchłych” czasach Java uruchamiała procesy przez metodę Runtime.getRuntime().exec(). Nie był to najwygodniejszy ani najbezpieczniejszy sposób — jak próba wbijania gwoździa mikroskopem. Dlatego od Javy 5 pojawiła się klasa ProcessBuilder, która pozwala tworzyć, konfigurować i uruchamiać procesy zewnętrzne w bardziej elastyczny i zrozumiały sposób.
ProcessBuilder to swoisty „konstruktor”, który pozwala z góry ustawić wszystkie parametry przyszłego procesu: polecenie, argumenty, katalog roboczy, zmienne środowiskowe itd.
Składnia: tworzenie procesu
ProcessBuilder pb = new ProcessBuilder("polecenie", "argument1", "argument2", ...);
- Pierwszy argument — nazwa polecenia (np. "ls", "dir", "ping", "java").
- Pozostałe — parametry polecenia.
Przykład: uruchomienie polecenia ls (Linux/Mac) lub dir (Windows)
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
Swoją drogą, w systemie Windows polecenia takie jak dir, copy itp. — to nie osobne pliki wykonywalne, lecz komendy „wbudowane” w wiersz poleceń (cmd.exe). Dlatego należy uruchamiać je przez cmd.exe /c ....
Przykład: uruchomienie prostego procesu
ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
3. Konfigurowanie środowiska procesu
Przekazywanie argumentów. Argumenty polecenia przekazuje się jako osobne łańcuchy:
ProcessBuilder pb = new ProcessBuilder("ping", "google.com");
Określenie katalogu roboczego. Domyślnie proces startuje w tym samym katalogu co twój program. Można jednak jawnie wskazać inny katalog:
pb.directory(new java.io.File("/tmp")); // Dla Linux/Mac
pb.directory(new java.io.File("C:\\Temp")); // Dla Windows
Zmiana zmiennych środowiskowych. Każdy proces ma własny zestaw zmiennych środowiskowych (environment variables). Można je dodać lub zmodyfikować:
pb.environment().put("MY_VAR", "HelloFromJava");
To bywa przydatne, jeśli proces zewnętrzny oczekuje określonych zmiennych.
4. Uruchamianie procesu
Metoda start(). Gdy wszystko skonfigurujesz, czas uruchomić proces:
Process process = pb.start();
Metoda start() zwraca obiekt Process, który pozwala zarządzać uruchomionym programem: czytać jego wyjście, pisać na jego wejście, zakańczać go itd.
Obsługa wyjątków. start() może zgłosić IOException, jeśli nie znaleziono polecenia, brakuje uprawnień albo wystąpił inny błąd uruchomienia.
Przykład:
try {
Process process = pb.start();
// Pracujemy z procesem...
} catch (IOException e) {
System.out.println("Błąd uruchamiania procesu: " + e.getMessage());
}
5. Praktyka: uruchamianie prostych poleceń
Przykład 1: wypisanie listy plików w katalogu
import java.io.*;
public class ProcessDemo {
public static void main(String[] args) {
// Ustalamy polecenie w zależności od systemu operacyjnego
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
try {
Process process = pb.start();
// Czytamy wyjście procesu (stdout)
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// Czekamy na zakończenie procesu
int exitCode = process.waitFor();
System.out.println("Proces zakończył się kodem: " + exitCode);
} catch (IOException | InterruptedException e) {
System.out.println("Błąd: " + e.getMessage());
}
}
}
Co tu się dzieje?
- Ustalamy, jaki to system operacyjny — aby wybrać właściwe polecenie.
- Tworzymy ProcessBuilder z odpowiednim poleceniem.
- Uruchamiamy proces przez start().
- Czytamy linie ze stdout procesu i wypisujemy je na ekran.
- Czekamy na zakończenie procesu (waitFor()).
- Wypisujemy kod zakończenia (0 — sukces, inna wartość — błąd).
Przykład 2: uruchomienie java -version
ProcessBuilder pb = new ProcessBuilder("java", "-version");
try {
Process process = pb.start();
// java -version pisze na stderr, więc czytamy getErrorStream()
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
Ważna uwaga: Niektóre polecenia (np. java -version) wypisują informacje nie na standardowe wyjście (stdout), lecz do strumienia błędów (stderr). Dlatego czasem trzeba czytać process.getErrorStream().
6. Wieloplatformowość: różnice między Windows a Linux/Mac
- Polecenia i ich parametry mogą się różnić.
- Ścieżki do plików zapisuje się inaczej (C:\Temp vs /tmp).
- Niektóre polecenia (np. ls, cat) istnieją tylko w systemach uniksowych, a w Windows zastępują je odpowiedniki (dir, type).
- W Windows polecenia wbudowane uruchamia się tylko przez cmd.exe /c polecenie.
Przykład sprawdzania systemu operacyjnego:
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows
} else if (os.contains("mac")) {
// macOS
} else if (os.contains("nix") || os.contains("nux")) {
// Linux
}
Wskazówka: Zawsze testuj swoje programy w docelowych systemach operacyjnych, jeśli liczysz na wieloplatformowość.
7. Tabela: najważniejsze metody i możliwości ProcessBuilder
| Metoda/pole | Przeznaczenie | Przykład użycia |
|---|---|---|
|
Utworzyć proces z poleceniem i argumentami | |
|
Ustawić katalog roboczy | |
|
Pobrać/zmienić zmienne środowiskowe | |
|
Uruchomić proces | |
|
Uzyskać stdout procesu | |
|
Uzyskać stderr procesu | |
|
Uzyskać stdin procesu | |
|
Czekać na zakończenie procesu | |
|
Pobrać kod zakończenia procesu | |
8. Typowe błędy przy uruchamianiu procesów zewnętrznych
Błąd nr 1: Nie znaleziono polecenia. Jeśli pomylisz się w nazwie polecenia lub go brakuje w systemie, otrzymasz IOException: Cannot run program .... Na przykład próba uruchomienia ls w Windows.
Błąd nr 2: Nieprawidłowe przekazywanie argumentów. Nie łącz całego polecenia w jeden łańcuch! Poprawnie: new ProcessBuilder("ping", "google.com"). Niepoprawnie: new ProcessBuilder("ping google.com").
Błąd nr 3: Zignorowano różnice systemów. Polecenie, które świetnie działa w Linuksie, może nie istnieć w Windows i odwrotnie. Zawsze sprawdzaj system i dostosuj polecenie.
Błąd nr 4: Nieobsłużone wyjście procesu. Jeśli nie będziesz czytać wyjścia procesu, może on „zawisnąć” z powodu przepełnienia bufora. Nawet jeśli nie zamierzasz używać wyjścia — czytaj je i, na przykład, po prostu ignoruj.
Błąd nr 5: Nie zamknięto strumieni. Strumienie procesu należy zamykać po użyciu, aby uniknąć wycieków zasobów.
Błąd nr 6: Nieobsłużone wyjątki. Uruchamianie procesu zewnętrznego jest obarczone ryzykiem. Zawsze używaj try-catch i informuj użytkownika o błędach.
GO TO FULL VERSION