ZetCode

Java Process 类

最后修改时间:2025 年 4 月 13 日

java.lang.Process 类提供了与系统进程交互的方法。它允许 Java 程序启动、控制和与原生操作系统进程进行通信。这对于系统级编程至关重要。

Process 对象由 Runtime.execProcessBuilder.start 方法创建。它们代表在 Java 虚拟机外部运行的本机进程。该类提供了获取输入/输出流、等待完成和检查退出状态的方法。

Process 类方法

Process 类提供了多种用于进程管理的方法。这些方法包括用于 I/O 流访问、进程终止和状态检查的方法。该类是抽象类,平台特定的实现由 JVM 提供。

public abstract class Process {
    public abstract OutputStream getOutputStream();
    public abstract InputStream getInputStream();
    public abstract InputStream getErrorStream();
    public abstract int waitFor() throws InterruptedException;
    public abstract int exitValue();
    public abstract void destroy();
    public Process destroyForcibly();
    public boolean isAlive();
    public boolean waitFor(long timeout, TimeUnit unit);
}

上面的代码显示了 Process 类提供的主要方法。这些方法允许控制外部进程并通过标准流进行通信。现代 Java 版本添加了更多方法,以实现更好的进程控制。

基本进程执行

此示例演示如何执行简单的系统命令并等待其完成。我们使用 Runtime.getRuntime().exec 启动进程。waitFor 方法会阻塞,直到进程完成。

Main.java
package com.zetcode;

import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        try {
            // Execute a system command
            Process process = Runtime.getRuntime().exec("notepad.exe");
            
            // Wait for the process to complete
            int exitCode = process.waitFor();
            
            System.out.println("Process exited with code: " + exitCode);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在本例中,我们启动 Windows 记事本并等待它关闭。waitFor 方法返回进程的退出代码。请注意,这是一个阻塞调用 - Java 程序将等待,直到记事本关闭。

读取进程输出

此示例演示如何读取进程的输出。我们执行一个产生输出的命令,并通过进程的输入流读取它。这对于捕获命令结果至关重要。

Main.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Main {

    public static void main(String[] args) {
        try {
            // Execute a command that produces output
            Process process = Runtime.getRuntime().exec("cmd /c dir");
            
            // Get the input stream of the process
            InputStream inputStream = process.getInputStream();
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(inputStream));
            
            // Read the output line by line
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            
            // Close resources
            reader.close();
            process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里,我们执行 Windows dir 命令并读取其输出。进程的标准输出可通过 getInputStream 访问。我们将它包装在 BufferedReader 中,以便高效地逐行读取。完成后始终关闭流,以防止资源泄漏。

处理进程错误

此示例演示如何从进程读取错误输出。错误输出可通过 getErrorStream 获取的单独流获得。正确的错误处理对于健壮的进程管理至关重要。

Main.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Main {

    public static void main(String[] args) {
        try {
            // Execute a command that might produce errors
            Process process = Runtime.getRuntime().exec("cmd /c dir nonexistent");
            
            // Read standard output
            readStream(process.getInputStream(), "OUTPUT");
            
            // Read error output
            readStream(process.getErrorStream(), "ERROR");
            
            process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    private static void readStream(InputStream inputStream, String type) 
            throws IOException {
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(inputStream));
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(type + ": " + line);
        }
        reader.close();
    }
}

在此示例中,我们尝试列出不存在的目录,这会生成错误输出。我们分别读取标准输出和错误流。错误流通常包含命令失败时的诊断消息。应始终读取两个流,以防止进程挂起。

写入进程输入

此示例演示如何将数据写入进程的输入流。某些程序接受通过其标准输入接收的输入。我们通过与 Python 解释器通信来演示这一点。

Main.java
package com.zetcode;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

public class Main {

    public static void main(String[] args) {
        try {
            // Start Python interpreter
            Process process = Runtime.getRuntime().exec("python");
            
            // Get the output stream to write to the process
            OutputStream outputStream = process.getOutputStream();
            BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(outputStream));
            
            // Write Python commands to the process
            writer.write("print(2 + 3)\n");
            writer.write("exit()\n");
            writer.flush();
            
            // Read the output
            readStream(process.getInputStream(), "PYTHON OUTPUT");
            
            process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    private static void readStream(java.io.InputStream inputStream, String type) 
            throws IOException {
        java.io.BufferedReader reader = new java.io.BufferedReader(
            new java.io.InputStreamReader(inputStream));
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(type + ": " + line);
        }
        reader.close();
    }
}

在这里,我们启动 Python 解释器并通过其标准输入向其发送命令。我们通过 getOutputStream 获取输出流,并将 Python 代码写入其中。写入后,我们必须刷新流以确保发送数据。然后从输入流读取 Python 输出。

ProcessBuilder 示例

此示例演示了用于进程创建的现代 ProcessBuilder 方法。ProcessBuilder 比 Runtime.exec() 提供对进程创建的更多控制。它允许设置环境变量和工作目录。

Main.java
package com.zetcode;

import java.io.IOException;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        try {
            // Create ProcessBuilder with command and arguments
            ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "echo", "Hello, ProcessBuilder!");
            
            // Redirect error stream to standard output
            pb.redirectErrorStream(true);
            
            // Start the process
            Process process = pb.start();
            
            // Read the combined output
            readStream(process.getInputStream(), "OUTPUT");
            
            int exitCode = process.waitFor();
            System.out.println("Exit code: " + exitCode);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    private static void readStream(java.io.InputStream inputStream, String type) 
            throws IOException {
        java.io.BufferedReader reader = new java.io.BufferedReader(
            new java.io.InputStreamReader(inputStream));
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(type + ": " + line);
        }
        reader.close();
    }
}

此示例使用 ProcessBuilder 执行带有参数的命令。我们设置 redirectErrorStream(true) 以将错误输出与标准输出合并。ProcessBuilder 比 Runtime.exec() 更受欢迎,因为它具有灵活性,并且可以更好地处理命令参数。它可以自动处理参数引用和拆分。

检查进程状态

此示例演示如何检查进程是否仍在运行并获取其退出值。isAlive 方法检查进程状态,而 exitValue 获取进程终止时的退出代码。

Main.java
package com.zetcode;

import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        try {
            // Start a long-running process
            Process process = Runtime.getRuntime().exec("ping -n 5 localhost");
            
            // Check process status periodically
            for (int i = 0; i < 10; i++) {
                if (process.isAlive()) {
                    System.out.println("Process is still running...");
                } else {
                    System.out.println("Process exited with code: " + 
                        process.exitValue());
                    break;
                }
                Thread.sleep(1000);
            }
            
            // If still running after 10 seconds, destroy it
            if (process.isAlive()) {
                System.out.println("Terminating process...");
                process.destroy();
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里,我们启动一个 ping 命令,该命令运行约 5 秒钟。我们使用 isAlive 定期检查其状态。如果进程在 10 秒后仍在运行,我们使用 destroy 终止它。请注意,如果在正在运行的进程上调用 exitValue,则会引发异常。

销毁进程

此示例演示了终止进程的不同方法。destroy 方法请求正常终止,而 destroyForcibly 尝试在需要时强制终止。

Main.java
package com.zetcode;

import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        try {
            // Start a process that will run indefinitely
            Process process = Runtime.getRuntime().exec("notepad.exe");
            
            System.out.println("Process started. Waiting 5 seconds...");
            Thread.sleep(5000);
            
            // Try to destroy the process gracefully
            System.out.println("Attempting to destroy process...");
            process.destroy();
            
            // Wait a bit to see if it terminates
            Thread.sleep(1000);
            
            if (process.isAlive()) {
                System.out.println("Process still alive. Forcing termination...");
                Process forced = process.destroyForcibly();
                forced.waitFor();
                System.out.println("Forced termination complete.");
            } else {
                System.out.println("Process terminated gracefully.");
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在本例中,我们启动记事本并尝试以编程方式关闭它。首先,我们尝试使用 destroy 进行正常终止。如果进程在一秒钟后仍然存活,我们使用 destroyForciblydestroyForcibly 方法返回一个 Process 对象,该对象可用于等待终止。这提供了对进程终止的更多控制。

来源

Java Process 类文档

在本文中,我们通过实际示例介绍了 Java Process 类。进程管理对于系统集成和外部命令执行至关重要。Process 类为 Java 中的这些操作提供了基础。

作者

我的名字是 Jan Bodnar,我是一位拥有多年经验的专业程序员。我于 2007 年开始撰写编程文章,至今已撰写了 1,400 多篇文章和 8 本电子书。凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程