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.
GO TO FULL VERSION