我有一个进程(在Windows上名为chromedriver.exe
(,它是在我创建Selenium Chrome Driver的新实例时创建的。
所以我不是自己开始这个过程,但如果可能的话,我想要一个代表该过程的java.lang.Process
实例。
我想创建这样的实例的原因是我想调用Process.waitFor()
等到我发出(如果在 Windows 上(Runtime.getRuntime().exec("taskkill /F /IM chromedriver.exe")
后进程实际终止。
我不能仅出于此特定需求引入对第三方库的新依赖。我可以使用Apache Commons的任何东西。
所以问题是:有没有办法从我的代码中获取一个表示未从我的代码启动的正在运行的进程的Process
实例?
我希望能够终止该进程的原因是,像 JVM 崩溃这样的突然终止可能不会WebDriver.quit()
终止该进程。因此,在创建导致进程启动的ChromeDriver实例之前,我想杀死任何可能已经存在的此类进程。
我之所以使用exec("taskkill...")
在 Windows 上杀死进程(我会在 Linux 上使用killall
(,是因为这是我迄今为止发现的唯一方法,但如果有可能获得一个代表该进程的Process
实例,我也会尝试使用Process.destroy()
来查看其描述"杀死子进程"中提到的"子进程"是否是指该进程。
简而言之,不是使用 Java ≤ 8,但可以使用Java 9和java.lang.ProcessHandle
。
Java 中的Process
并不是要对任意操作系统进程进行建模,它实际上是对java 进程的一个子进程进行建模,因此您不能附加到另一个正在运行的进程,并期望获得Process
具有的所有功能:
类 Process 提供了用于执行来自进程的输入、执行进程输出、等待进程完成、检查进程的退出状态以及销毁(终止(进程的方法。
如您所见,输入和输出要求您的 Java 进程对另一个进程的 stdin/stdout/stderr 有一个句柄,这只对子进程有意义。该文档还到处都提到了"子进程",从而暗示它是一个子进程。
在 Java 9 中,文档明确以以下内容开头:
进程提供对由
ProcessBuilder.start
和Runtime.exec
启动的本机进程的控制。
但是Java 9也带来了ProcessHandle
,这正是你所需要的:
ProcessHandle
识别并提供对本机进程的控制。可以监视每个进程的活动性,列出其子进程,获取有关进程的信息或销毁进程。相比之下,Process
实例由当前进程启动,并且还提供对进程输入、输出和错误流的访问。
因此,如果您可以使用Java 9运行测试,则可以尝试使用ProcessHandle.allProcesses()
来查找所需的进程,然后使用.destroy()
杀死它,使用isAlive()
监视其活动性,或使用onExit()
。
Optional<ProcessHandle> optional = ProcessHandle.allProcesses().filter(process -> {
Optional<String> command = process.info().command();
return command.isPresent() && command.get().equals("Chromedriver.exe");
}).findFirst();
if (optional.isPresent()) {
ProcessHandle processHandle = optional.get();
System.out.println("Killing process " + processHandle.pid());
processHandle.destroy();
try {
System.out.println("Waiting for process " + processHandle.pid() + " to exit...");
processHandle.onExit().get();
System.out.println("Done !");
} catch (InterruptedException|ExecutionException e) {
e.printStackTrace();
}
}
话虽如此,如果你真的愿意,你可以自己创建一个Process
子类,它将提供有限的功能(即没有关于I/O的内容,只有waitFor
和destroy
等(。
下面是一个可怕的例子,使用这个问答中的想法快速破解了Windows实现。
- 使用
tasklist /FO CSV /FI "IMAGENAME eq Chromedriver.exe", to get the PID
按名称查找进程 - 用
taskkill
杀死它 - 等待它以使用
tasklist /FO CSV /FI "PID eq ..."
的轮询循环结束
Linux 版本使用pgrep
、kill
和/proc/{pid}/
。
请注意,将其包装为java.lang.Process
的实际实例并不是很有用,因此我用一个实现窗口所需功能的工具类和作为练习实现的Process
子类来区分这些问题,以显示什么是可能的,什么是不可能的(代码中的详细信息(。因此,如果您需要它,最好直接使用ProcessTool
,而不是DummyProcess
.
(我已经在另一个过程中测试过了,我不能说Chromedriver.exe
,你可能需要调整一些细节(
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Paths;
import java.util.Scanner;
public class WaitForProcessDemo {
public static void main(String[] args) throws IOException, InterruptedException {
Process p = getDummyProcess("Chromedriver");
System.out.println("Killing...");
p.destroy();
System.out.println("Waiting...");
p.waitFor();
System.out.println("Done.");
}
private static Process getDummyProcess(String exeName) throws IOException {
ProcessTool tool = new WindowsProcessTool();
long pid = tool.findProcessByName(exeName);
return new DummyProcess(pid, tool);
}
interface ProcessTool {
long findProcessByName(String exeName) throws IOException;
void killProcess(long pid) throws IOException;
boolean isPidStillThere(long pid);
}
private static class WindowsProcessTool implements ProcessTool {
@Override
public long findProcessByName(String exeName) throws IOException {
String processInfoCSV = findProcessInfoCSV("IMAGENAME eq " + exeName);
String[] fields = processInfoCSV.split(""");
String pid = fields[3];
return Integer.parseInt(pid);
}
String findProcessInfoCSV(String filter) throws IOException {
Process p = new ProcessBuilder("tasklist", "-FO", "CSV", "/FI", filter)
.redirectErrorStream(true)
.start();
Scanner scanner = new Scanner(p.getInputStream());
scanner.nextLine(); // skip header line
if (scanner.hasNextLine()) {
return scanner.nextLine();
}
throw new IOException("No such process: " + filter);
}
@Override
public void killProcess(long pid) throws IOException {
new ProcessBuilder("taskkill", "/PID", String.valueOf(pid))
.redirectErrorStream(true)
.start();
}
@Override
public boolean isPidStillThere(long pid) {
try {
findProcessInfoCSV("PID eq " + pid);
return true;
} catch (IOException e) {
return false;
}
}
}
private static class LinuxProcessTool implements ProcessTool {
@Override
public long findProcessByName(String exeName) throws IOException {
Process pgrep = new ProcessBuilder("pgrep", exeName)
.redirectErrorStream(true)
.start();
Scanner scanner = new Scanner(pgrep.getInputStream());
return Long.parseLong(scanner.nextLine());
}
@Override
public void killProcess(long pid) throws IOException {
new ProcessBuilder("kill", String.valueOf(pid))
.redirectErrorStream(true)
.start();
}
@Override
public boolean isPidStillThere(long pid) {
return Paths.get("/proc", String.valueOf(pid)).toFile().isDirectory();
}
}
/*
* Broken & incomplete implementation of java.lang.Process, implemented as an exercise.
* (Kids, don't do this at home)
*/
static class DummyProcess extends Process {
private final long pid;
private final ProcessTool tool;
DummyProcess(long pid, ProcessTool tool) {
this.pid = pid;
this.tool = tool;
}
@Override
public OutputStream getOutputStream() {
return null; // DANGER. This cannot be implemented for non-child process.
}
@Override
public InputStream getInputStream() {
return null; // DANGER. This cannot be implemented for non-child process.
}
@Override
public InputStream getErrorStream() {
return null; // DANGER. This cannot be implemented for non-child process.
}
@Override
public int waitFor() throws InterruptedException {
// Very sub-optimal implementation
boolean isPidPresent = isPidStillThere();
while (isPidPresent) {
Thread.sleep(500);
isPidPresent = isPidStillThere();
}
return 0;
}
@Override
public int exitValue() {
// For example, this is dangerous, as Process.isAlive() will call this, and determine process is not alive.
// Also, it cannot be implemented correctly, it's not possible to tell what was exit value.
// At best we could throw IllegalThreadStateException when process is still alive.
return 0;
}
@Override
public void destroy() {
try {
tool.killProcess(pid);
} catch (IOException e) {
throw new RuntimeException("Failed to kill process " + pid, e);
}
}
private boolean isPidStillThere() {
return tool.isPidStillThere(pid);
}
}
}