CodeGym /Kursy /JAVA 25 SELF /Zarządzanie cyklem życia procesu

Zarządzanie cyklem życia procesu

JAVA 25 SELF
Poziom 61 , Lekcja 2
Dostępny

1. Oczekiwanie na zakończenie procesu: waitFor()

Gdy uruchamiasz proces zewnętrzny z Javy, żyje on własnym życiem: coś oblicza, wypisuje do konsoli, czasem się zawiesza (pozdrawiamy ping na milion pakietów). Ale czasem musimy wiedzieć, kiedy ten proces się zakończył, jak się zakończył (sukces czy porażka) i – jeśli coś poszło nie tak – potrafić go „zabić”. Krótko mówiąc, jak w prawdziwym życiu: jeśli komuś zlecisz zadanie, dobrze wiedzieć, czy sobie poradził, i mieć możliwość go zatrzymać, jeśli nagle zacznie bez końca parzyć kawę.

Gdy uruchamiasz proces przez ProcessBuilder, otrzymujesz obiekt typu Process. Ten obiekt to jak pilot: dzięki niemu możesz czekać na zakończenie procesu, pobrać kod wyjścia, zakończyć proces, a także zarządzać strumieniami wejścia/wyjścia.

Jak czekać na zakończenie procesu?

Służy do tego metoda waitFor():

ProcessBuilder builder = new ProcessBuilder("ping", "google.com", "-c", "3");
Process process = builder.start();

System.out.println("Czekamy na zakończenie procesu...");
int exitCode = process.waitFor(); // Blokuje bieżący wątek do zakończenia procesu
System.out.println("Proces zakończył się z kodem: " + exitCode);

Co się dzieje?

  • Program uruchamia proces zewnętrzny (ping).
  • Następnie wątek, w którym wywołano waitFor(), „zasypia” do czasu zakończenia procesu zewnętrznego.
  • Gdy tylko proces zewnętrzny zakończy pracę, waitFor() zwraca kod wyjścia (zwykle 0 – sukces, różne od 0 – błąd).

Jak poznać kod wyjścia?

Metoda waitFor() zwraca ten kod. Można go też pobrać osobno przez exitValue():

int code = process.exitValue();

Uwaga: jeśli proces jeszcze się nie zakończył, wywołanie exitValue() wyrzuci wyjątek IllegalThreadStateException. Dlatego zwykle najpierw czeka się na zakończenie przez waitFor(), a potem sprawdza kod.

2. Przerywanie procesu: destroy() i destroyForcibly()

Czasem proces zewnętrzny działa zbyt długo albo się zawiesza. Na przykład ktoś uruchomił ping na 1000 pakietów, a termin macie za 5 sekund. Potrzebny jest „czerwony przycisk”.

Łagodne zakończenie: destroy()

Metoda destroy() wysyła do procesu sygnał zakończenia (SIGTERM w Unix, CTRL-BREAK w Windows), dając mu szansę na poprawne zamknięcie.

Process process = new ProcessBuilder("ping", "google.com").start();

Thread.sleep(2000); // Niech popracuje 2 sekundy
process.destroy(); // Poprośmy o zakończenie

Twarde zakończenie: destroyForcibly()

Jeśli proces nie reaguje na uprzejme prośby, można użyć metody destroyForcibly(). To jak wyciągnięcie wtyczki z gniazdka.

process.destroyForcibly();

Ważne: Po wywołaniu destroy() lub destroyForcibly() należy poczekać na zakończenie procesu przez waitFor(). Czasem proces potrzebuje chwili, żeby „uświadomić sobie” swoją śmierć.

3. Oczekiwanie na proces z limitem czasu

W Javie 8+ pojawiła się metoda z limitem czasu:

boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);
if (finished) {
    System.out.println("Proces zakończył się na czas!");
} else {
    System.out.println("Proces się nie zakończył, przerywamy...");
    process.destroy();
}
  • Jeśli proces zakończy się w ciągu 5 sekund, metoda zwróci true.
  • Jeśli nie – zwróci false i możesz podjąć działania (np. zakończyć proces).

Przykład: Uruchommy długotrwałe polecenie i spróbujmy je „zabić”:

ProcessBuilder builder = new ProcessBuilder("ping", "google.com", "-c", "10");
Process process = builder.start();

boolean finished = process.waitFor(2, java.util.concurrent.TimeUnit.SECONDS);
if (!finished) {
    System.out.println("Proces działa zbyt długo, przerywamy...");
    process.destroy();
    // Można poczekać na pełne zakończenie
    process.waitFor();
}
System.out.println("Kod wyjścia: " + process.exitValue());

4. Praktyka: mini-narzędzie do uruchamiania polecenia z limitem czasu

Napiszmy prosty program, który uruchamia zewnętrzne polecenie z limitem czasu, czeka na jego zakończenie, a po przekroczeniu czasu – kończy proces.

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class ProcessTimeoutDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        Scanner scanner = new Scanner(System.in);

        System.out.println("Wpisz polecenie do uruchomienia (np. ping google.com):");
        String commandLine = scanner.nextLine();
        String[] command = commandLine.split(" ");

        ProcessBuilder builder = new ProcessBuilder(command);
        Process process = builder.start();

        System.out.println("Ile sekund czekać na zakończenie?");
        int timeout = scanner.nextInt();

        boolean finished = process.waitFor(timeout, TimeUnit.SECONDS);
        if (finished) {
            System.out.println("Proces zakończył się sam. Kod wyjścia: " + process.exitValue());
        } else {
            System.out.println("Czas minął! Kończymy proces...");
            process.destroy();
            process.waitFor();
            System.out.println("Proces został zakończony przymusowo. Kod wyjścia: " + process.exitValue());
        }
    }
}

Wyjaśnienie:

  • Użytkownik wpisuje polecenie, które należy uruchomić.
  • Program czeka podany czas.
  • Jeśli proces się nie zakończył – przerywa go.

Wskazówka: W Windows ping domyślnie pinguje 4 razy, a w Linux – bez końca. Dla Linux/Mac dodaj parametr -c 5 (np. ping google.com -c 5).

5. Kod wyjścia: co to jest i po co?

W świecie wiersza poleceń przyjęto, że jeśli polecenie zakończyło się pomyślnie, zwraca kod 0. Jeśli coś poszło nie tak – dowolny inny kod.

  • W systemach uniksowych: 0 = sukces, 1 i więcej = błąd.
  • W Windows – zasadniczo tak samo.

Możesz użyć tego kodu do podejmowania decyzji w programie w Javie:

int exitCode = process.waitFor();
if (exitCode == 0) {
    System.out.println("Sukces!");
} else {
    System.out.println("Błąd! Kod: " + exitCode);
}

6. Kończenie zawieszonych procesów

Czasem proces może po prostu się zawiesić – np. czeka na dane wejściowe albo nie może połączyć się z siecią. Jeśli zostawisz go bez opieki, będzie wisiał w systemie i zupełnie spokojnie pożerał zasoby.

Aby do tego nie dopuścić, warto wyznaczyć procesowi granicę cierpliwości. Metoda waitFor(timeout, unit) pozwala czekać na jego zakończenie tylko określony czas. Jeśli proces się nie wyrobi – śmiało go zakańczaj.

Po wywołaniu destroy() ważne jest nie tylko machnąć na to ręką, lecz upewnić się, że proces rzeczywiście się zakończył. W tym celu ponownie wywołuje się waitFor(). A jeśli i to nie pomogło – jest środek ostateczny, destroyForcibly(). Przerywa wszystko bez zbędnych ceregieli.

Przykład z podwójnym zakończeniem

boolean finished = process.waitFor(5, TimeUnit.SECONDS);
if (!finished) {
    process.destroy();
    if (!process.waitFor(2, TimeUnit.SECONDS)) {
        process.destroyForcibly();
    }
}

Cechy pracy z Process: o czym warto pamiętać

Gdy proces się zakończy, nie spiesz się, by o nim zapomnieć. Zajrzyj do jego wyjść – zarówno zwykłego, jak i tego z błędami. Może się tam znaleźć coś istotnego: komunikat o awarii, ostrzeżenie lub po prostu potwierdzenie, że wszystko poszło pomyślnie.

Następnie koniecznie zamknij wszystkie powiązane strumienie – InputStream, OutputStream i ErrorStream. To jak zgaszenie światła po wyjściu: oszczędza zasoby i utrzymuje porządek w systemie.

I pamiętaj: jeśli zatrzymasz proces przymusowo, może on nie zdążyć poprawnie zapisać swojego wyjścia. Nic strasznego – to naturalne zachowanie w takich sytuacjach.

7. Przykład: uruchomienie długiego polecenia i jego zakończenie

Załóżmy, że chcemy uruchomić długotrwały proces (np. ping z dużą liczbą pakietów) i zakończyć go po 3 sekundach.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;

public class KillLongProcessDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        String[] command = {"ping", "google.com"};
        // Dla Linux/Mac: {"ping", "google.com", "-c", "100"}
        ProcessBuilder builder = new ProcessBuilder(command);
        Process process = builder.start();

        // Czytamy wyjście procesu w osobnym wątku
        Thread readerThread = new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                // Ignorujemy
            }
        });
        readerThread.start();

        // Czekamy 3 sekundy
        if (!process.waitFor(3, TimeUnit.SECONDS)) {
            System.out.println("Proces działa zbyt długo, przerywamy...");
            process.destroy();
            process.waitFor();
        }
        readerThread.join(); // Poczekaj na odczyt wyjścia
        System.out.println("Proces zakończony. Kod wyjścia: " + process.exitValue());
    }
}

8. Typowe błędy przy zarządzaniu procesami

Błąd nr 1: Nie czekać na zakończenie procesu. Jeśli uruchomiłeś proces i po prostu o nim zapomniałeś (nie wywołałeś waitFor()), to proces może pozostać „wiszący” w systemie nawet po zakończeniu twojego programu w Javie. Strumienie wejścia/wyjścia mogą nie zostać zamknięte, co doprowadzi do wycieków pamięci i deskryptorów plików.

Błąd nr 2: Nieprawidłowe zakończenie procesu. Wywołano destroy(), ale nie poczekano na zakończenie procesu przez waitFor(). W rezultacie proces może pozostać „zombie” (zwłaszcza w systemach uniksowych).

Błąd nr 3: Oczekiwanie na kod wyjścia przed zakończeniem procesu. Wywołanie exitValue() przed zakończeniem procesu prowadzi do wyjątku IllegalThreadStateException.

Błąd nr 4: Brak obsługi wyjątków. Metody pracy z procesami mogą wyrzucać IOException i InterruptedException. Nie zapominaj ich przechwytywać albo zadeklarować w sygnaturze metody.

Błąd nr 5: Nieczytanie wyjścia procesu. Jeśli nie czytasz stdout i stderr, proces może się zawiesić z powodu przepełnienia bufora wyjścia.

1
Zadanie
JAVA 25 SELF, poziom 61, lekcja 2
Niedostępne
Awaryjne zatrzymanie systemu 🚨
Awaryjne zatrzymanie systemu 🚨
1
Zadanie
JAVA 25 SELF, poziom 61, lekcja 2
Niedostępne
Kontrola startu rakiety 🛰️
Kontrola startu rakiety 🛰️
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION