如何列出被org.apache.commons.exec.ExecuteWatchDog终止的进程?
记录被ExecuteWatchDog销毁的进程方法
因为org.apache.commons.exec.ExecuteWatchDog本身没有提供直接记录被销毁进程的API,你可以通过以下两种方式实现需求:
方法一:扩展ExecuteWatchDog类(推荐)
重写WatchDog的destroyProcess方法——这个方法是WatchDog销毁超时进程时的核心调用点,在里面添加进程信息记录逻辑即可。
自定义追踪版WatchDog代码
import org.apache.commons.exec.ExecuteWatchDog; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.lang.reflect.Field; public class TrackingExecuteWatchDog extends ExecuteWatchDog { private final List<ProcessRecord> destroyedProcesses = Collections.synchronizedList(new ArrayList<>()); private String command; public TrackingExecuteWatchDog(long timeout) { super(timeout); } // 设置要执行的命令,用于记录 public void setCommand(String command) { this.command = command; } @Override protected void destroyProcess(Process process) { // 先执行父类的销毁逻辑 super.destroyProcess(process); // 获取进程PID(兼容JDK8及以上) long pid = getProcessPid(process); // 记录销毁信息 destroyedProcesses.add(new ProcessRecord(pid, command, LocalDateTime.now())); } // 提供外部获取被销毁进程列表的方法 public List<ProcessRecord> getDestroyedProcesses() { return new ArrayList<>(destroyedProcesses); } // 内部类存储进程销毁记录 public static class ProcessRecord { private final long pid; private final String command; private final LocalDateTime destroyTime; public ProcessRecord(long pid, String command, LocalDateTime destroyTime) { this.pid = pid; this.command = command; this.destroyTime = destroyTime; } // getter方法 public long getPid() { return pid; } public String getCommand() { return command; } public LocalDateTime getDestroyTime() { return destroyTime; } } // 私有方法:获取进程PID private long getProcessPid(Process process) { // JDK9+ 直接用ProcessHandle if (process instanceof java.lang.ProcessHandle) { return ((java.lang.ProcessHandle) process).pid(); } // JDK8及以下用反射获取PID try { Field pidField = process.getClass().getDeclaredField("pid"); pidField.setAccessible(true); return pidField.getLong(process); } catch (Exception e) { return -1; // 标记无法获取PID } } }
使用方式
import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import java.io.IOException; import java.util.List; public class ExecDemo { public static void main(String[] args) throws IOException, InterruptedException { // 定义要执行的命令 CommandLine cmdLine = CommandLine.parse("sleep 60"); // 初始化自定义WatchDog,设置30秒超时 TrackingExecuteWatchDog watchDog = new TrackingExecuteWatchDog(30000); watchDog.setCommand(cmdLine.toString()); // 关联到DefaultExecutor DefaultExecutor executor = new DefaultExecutor(); executor.setWatchdog(watchDog); // 执行命令 try { executor.execute(cmdLine); } catch (Exception e) { // 处理执行异常(超时会抛出ExecuteException) } // 查看被销毁的进程记录 List<TrackingExecuteWatchDog.ProcessRecord> destroyed = watchDog.getDestroyedProcesses(); for (TrackingExecuteWatchDog.ProcessRecord record : destroyed) { System.out.printf("被销毁进程:PID=%d,命令=%s,销毁时间=%s%n", record.getPid(), record.getCommand(), record.getDestroyTime()); } } }
方法二:通过ExecuteResultHandler回调判断
利用ExecuteResultHandler的失败回调,结合ExecuteWatchDog#killedProcess()方法判断是否是超时销毁,再记录进程信息。
代码示例
import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteException; import org.apache.commons.exec.ExecuteResultHandler; import org.apache.commons.exec.ExecuteWatchDog; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ExecResultHandlerDemo { public static void main(String[] args) throws InterruptedException { CommandLine cmdLine = CommandLine.parse("sleep 60"); long timeout = 30000; ExecuteWatchDog watchDog = new ExecuteWatchDog(timeout); List<ProcessRecord> destroyedProcesses = Collections.synchronizedList(new ArrayList<>()); DefaultExecutor executor = new DefaultExecutor(); executor.setWatchdog(watchDog); ExecuteResultHandler resultHandler = new ExecuteResultHandler() { @Override public void onProcessComplete(int exitValue) { // 进程正常结束,无需记录 } @Override public void onProcessFailed(ExecuteException e) { // 判断是否是WatchDog销毁的进程 if (watchDog.killedProcess()) { long pid = getProcessPid(e.getProcess()); destroyedProcesses.add(new ProcessRecord(pid, cmdLine.toString(), LocalDateTime.now())); } } }; // 异步执行命令 try { executor.execute(cmdLine, resultHandler); } catch (Exception e) { // 处理提交异常 } // 等待执行完成 resultHandler.waitFor(); // 输出记录 for (ProcessRecord record : destroyedProcesses) { System.out.printf("被销毁进程:PID=%d,命令=%s,销毁时间=%s%n", record.getPid(), record.getCommand(), record.getDestroyTime()); } } // 复用之前的getProcessPid方法 private static long getProcessPid(Process process) { if (process instanceof java.lang.ProcessHandle) { return ((java.lang.ProcessHandle) process).pid(); } try { Field pidField = process.getClass().getDeclaredField("pid"); pidField.setAccessible(true); return pidField.getLong(process); } catch (Exception e) { return -1; } } private static class ProcessRecord { private final long pid; private final String command; private final LocalDateTime destroyTime; public ProcessRecord(long pid, String command, LocalDateTime destroyTime) { this.pid = pid; this.command = command; this.destroyTime = destroyTime; } public long getPid() { return pid; } public String getCommand() { return command; } public LocalDateTime getDestroyTime() { return destroyTime; } } }
注意事项
- PID兼容性:JDK9及以上可以直接用
ProcessHandle获取PID,JDK8及以下需要通过反射(不同JVM实现可能略有差异,比如OpenJDK和OracleJDK的Process类PID字段名一致,但其他JVM可能不同)。 - 线程安全:如果是多线程执行命令,要确保记录进程信息的集合是线程安全的(比如用
Collections.synchronizedList或ConcurrentLinkedQueue)。 - 命令信息传递:需要在执行前把命令字符串传递到记录逻辑中,避免无法关联进程对应的命令。
内容的提问来源于stack exchange,提问作者Raman
相关产品推荐
相关产品推荐

