CodeGym /Cursos /JAVA 25 SELF /Gerenciando o ciclo de vida de processos

Gerenciando o ciclo de vida de processos

JAVA 25 SELF
Nível 61 , Lição 2
Disponível

1. Aguardando a finalização do processo: waitFor()

Quando você inicia um processo externo a partir do Java, ele vive a própria vida: faz cálculos, escreve no console, às vezes trava (olá, ping com um milhão de pacotes). Mas às vezes precisamos saber quando esse processo terminou, como ele terminou (sucesso ou falha) e — se algo saiu errado — poder “matá-lo”. No geral, é como na vida real: se você delegou uma tarefa a alguém, é bom saber se a pessoa cumpriu e ter a opção de pará-la, caso comece a fazer café infinitamente.

Ao iniciar um processo com ProcessBuilder, você obtém um objeto do tipo Process. Esse objeto funciona como um controle remoto: com ele, dá para aguardar o término do processo, obter o código de saída, encerrar o processo e gerenciar os fluxos de entrada e saída.

Como aguardar o término do processo?

Para isso, existe o método waitFor():

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

System.out.println("Aguardando a finalização do processo...");
int exitCode = process.waitFor(); // Bloqueia a thread atual até o término do processo
System.out.println("O processo terminou com o código: " + exitCode);

O que acontece?

  • O programa inicia um processo externo (ping).
  • Em seguida, a thread na qual waitFor() foi chamada “dorme” até o processo externo terminar.
  • Assim que o processo externo conclui, waitFor() retorna o código de saída (normalmente 0 = sucesso; diferente de 0 = erro).

Como obter o código de saída?

O método waitFor() retorna esse código. Também é possível obtê-lo separadamente com exitValue():

int code = process.exitValue();

Mas tenha cuidado: se o processo ainda não terminou, chamar exitValue() lançará a exceção IllegalThreadStateException. Por isso, geralmente se aguarda primeiro com waitFor() e só então se consulta o código.

2. Interrompendo o processo: destroy() e destroyForcibly()

Às vezes, o processo externo leva tempo demais ou trava. Por exemplo, alguém executou ping com 1000 pacotes, e o seu prazo expira em 5 segundos. Precisamos de um “botão vermelho”.

Encerramento suave: destroy()

O método destroy() envia um sinal de término (SIGTERM no Unix, CTRL-BREAK no Windows), e o processo tem a chance de encerrar corretamente.

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

Thread.sleep(2000); // Deixe rodar por 2 segundos
process.destroy(); // Solicitar encerramento

Encerramento forçado: destroyForcibly()

Se o processo não responder a pedidos educados, você pode usar o método destroyForcibly(). É como puxar o plugue da tomada.

process.destroyForcibly();

Importante: Após chamar destroy() ou destroyForcibly(), é preciso aguardar o término do processo com waitFor(). Às vezes o processo precisa de tempo para “perceber” que foi encerrado.

3. Aguardando um processo com timeout

No Java 8+ surgiu um método com timeout:

boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);
if (finished) {
    System.out.println("O processo terminou a tempo!");
} else {
    System.out.println("O processo não terminou, vamos encerrar...");
    process.destroy();
}
  • Se o processo terminar em até 5 segundos, o método retornará true.
  • Caso contrário — retornará false, e você pode tomar providências (por exemplo, encerrar o processo).

Exemplo: Vamos iniciar um comando demorado e tentar “matá-lo”:

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("O processo está demorando demais, encerrando...");
    process.destroy();
    // Podemos aguardar o encerramento completo
    process.waitFor();
}
System.out.println("Código de saída: " + process.exitValue());

4. Prática: miniutilitário para executar um comando com timeout

Vamos escrever um programa simples que executa um comando externo com timeout, aguarda sua finalização e, ao exceder o tempo — encerra o processo.

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("Digite o comando a executar (por exemplo, ping google.com):");
        String commandLine = scanner.nextLine();
        String[] command = commandLine.split(" ");

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

        System.out.println("Quantos segundos devemos aguardar pelo término?");
        int timeout = scanner.nextInt();

        boolean finished = process.waitFor(timeout, TimeUnit.SECONDS);
        if (finished) {
            System.out.println("O processo terminou sozinho. Código de saída: " + process.exitValue());
        } else {
            System.out.println("Tempo esgotado! Encerrando o processo...");
            process.destroy();
            process.waitFor();
            System.out.println("Processo encerrado à força. Código de saída: " + process.exitValue());
        }
    }
}

Explicação:

  • O usuário informa o comando que precisa ser executado.
  • O programa aguarda o tempo especificado.
  • Se o processo não terminar — ele é encerrado.

Dica: No Windows, ping por padrão pinga 4 vezes, e no Linux — indefinidamente. Para Linux/Mac, adicione o parâmetro -c 5 (por exemplo, ping google.com -c 5).

5. Obtendo o código de saída: o que é e para que serve?

No mundo das linhas de comando é comum: se o comando termina com sucesso, ele retorna 0. Se algo deu errado — qualquer outro código.

  • Em sistemas Unix-like: 0 = sucesso, 1 ou mais = erro.
  • No Windows — de forma semelhante.

Você pode usar esse código para tomar decisões no seu programa Java:

int exitCode = process.waitFor();
if (exitCode == 0) {
    System.out.println("Sucesso!");
} else {
    System.out.println("Erro! Código: " + exitCode);
}

6. Encerrando processos travados

Às vezes, um processo pode simplesmente travar — por exemplo, fica esperando que você digite algo ou não consegue se conectar à rede. Se deixá-lo sem atenção, ele continuará pendurado no sistema consumindo recursos.

Para evitar isso, vale a pena definir um limite de paciência para o processo. O método waitFor(timeout, unit) permite aguardar sua finalização por um tempo determinado. Se o processo não terminar nesse prazo — encerre sem medo.

Após chamar destroy(), não basta “deixar pra lá”; é importante garantir que o processo realmente terminou. Para isso, chame novamente waitFor(). E, se nem isso ajudar — existe a medida extrema, destroyForcibly(), que interrompe tudo sem mais conversas.

Exemplo com dupla tentativa de encerramento

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

Particularidades ao trabalhar com Process: o que é importante lembrar

Quando o processo terminar, não se apresse em esquecê-lo. Confira a saída — tanto a padrão quanto a de erros. Pode haver algo importante ali: uma mensagem de falha, um aviso ou simplesmente a confirmação de que tudo correu bem.

Depois disso, feche todos os fluxos associados — InputStream, OutputStream e ErrorStream. É como apagar a luz ao sair: economiza recursos e mantém a organização do sistema.

E lembre-se: se você interrompeu o processo à força, ele pode não ter tempo de gravar sua saída corretamente. Não há problema — isso é um comportamento esperado nesse tipo de situação.

7. Exemplo: iniciar um comando demorado e encerrá-lo

Suponha que queremos iniciar um processo demorado (por exemplo, ping com um número grande de pacotes) e encerrá-lo após 3 segundos.

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"};
        // Para Linux/Mac: {"ping", "google.com", "-c", "100"}
        ProcessBuilder builder = new ProcessBuilder(command);
        Process process = builder.start();

        // Lemos a saída do processo em uma thread separada
        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) {
                // Ignorar
            }
        });
        readerThread.start();

        // Aguardamos 3 segundos
        if (!process.waitFor(3, TimeUnit.SECONDS)) {
            System.out.println("O processo está demorando demais, encerrando...");
            process.destroy();
            process.waitFor();
        }
        readerThread.join(); // Aguardar a leitura da saída
        System.out.println("Processo encerrado. Código de saída: " + process.exitValue());
    }
}

8. Erros comuns ao gerenciar processos

Erro nº 1: Não aguardar o término do processo. Se você iniciou um processo e simplesmente o esqueceu (não chamou waitFor()), o processo pode ficar “pendurado” no sistema mesmo após a finalização do seu programa Java. Os fluxos de entrada/saída podem permanecer abertos, o que leva a vazamentos de memória e de descritores de arquivo.

Erro nº 2: Encerramento incorreto do processo. Você chamou destroy(), mas não aguardou o término com waitFor(). Como resultado, o processo pode virar um “zumbi” (especialmente em sistemas Unix).

Erro nº 3: Aguardar o código de saída antes do término do processo. Chamar exitValue() antes de o processo terminar resulta em IllegalThreadStateException.

Erro nº 4: Não tratar exceções. Os métodos de trabalho com processos podem lançar IOException e InterruptedException. Não se esqueça de capturá-las ou declará-las na assinatura do método.

Erro nº 5: Não ler a saída do processo. Se você não ler o stdout e o stderr, o processo pode travar devido ao estouro do buffer de saída.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION