|
@@ -1,6 +1,6 @@
|
|
|
# JAVA Process 使用教程
|
|
# JAVA Process 使用教程
|
|
|
|
|
|
|
|
-本教程通过 6 个可运行的示例,讲解 `ProcessBuilder` / `Process` 的核心用法。所有示例都以
|
|
|
|
|
|
|
+本教程通过 7 个可运行的示例,讲解 `ProcessBuilder` / `Process` 的核心用法。所有示例都以
|
|
|
`src/main/resources/code/` 下的 `.java` 程序作为子进程,先编译再运行。
|
|
`src/main/resources/code/` 下的 `.java` 程序作为子进程,先编译再运行。
|
|
|
每个**案例点**由独立的 `static` method 演示,在 `main()` 中依次调用。
|
|
每个**案例点**由独立的 `static` method 演示,在 `main()` 中依次调用。
|
|
|
|
|
|
|
@@ -312,6 +312,7 @@ int exitCode = process.exitValue(); // 被信号终止时通常为 143(SIGTER
|
|
|
| `destroyForcibly()` | 无 | `Process` | 强制终止(Linux 为 SIGKILL) |
|
|
| `destroyForcibly()` | 无 | `Process` | 强制终止(Linux 为 SIGKILL) |
|
|
|
| `isAlive()` | 无 | `boolean` | 进程是否仍在运行 |
|
|
| `isAlive()` | 无 | `boolean` | 进程是否仍在运行 |
|
|
|
| `onExit()` | 无 | `CompletableFuture<Process>` | 进程结束时自动完成 |
|
|
| `onExit()` | 无 | `CompletableFuture<Process>` | 进程结束时自动完成 |
|
|
|
|
|
+| `toHandle()` | 无 | `ProcessHandle` | 转为 JDK 9+ 的进程句柄,可查询元信息、遍历进程树、按句柄销毁(见第六章) |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -359,10 +360,117 @@ stdout"换成业务处理(如逐行解析、透传到日志),子进程就
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
|
|
+## 六、使用 ProcessHandle 管理进程(进阶) —— `ProcessHandleExample`
|
|
|
|
|
+
|
|
|
|
|
+> **进阶章节**:ProcessHandle 是 JDK 9 新增的进程管理 API,需要 JDK 9+ 环境(本教程 target 17,没问题)。
|
|
|
|
|
+
|
|
|
|
|
+案例点:`handleFromProcess()` / `resolveByPid()` / `inspectProcessTree()` / `terminateViaHandle()`(示例类:`space.anyi.process.ProcessHandleExample`)
|
|
|
|
|
+
|
|
|
|
|
+子进程(`code/processHandle/Main.java`)启动后打印自身 pid 与 args,然后睡眠 30 秒等父进程检查与销毁。四个案例点各演示 ProcessHandle 的一个能力:
|
|
|
|
|
+
|
|
|
|
|
+### 从 Process 获取句柄并查询元信息 —— `handleFromProcess()`
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ProcessHandle handle = process.toHandle(); // Process → ProcessHandle
|
|
|
|
|
+handle.pid(); // 与 process.pid() 一致
|
|
|
|
|
+handle.isAlive(); // 进程是否存活
|
|
|
|
|
+
|
|
|
|
|
+ProcessHandle.Info info = handle.info(); // 全部 Optional
|
|
|
|
|
+info.command(); // 如 Optional[/path/to/java]
|
|
|
|
|
+info.commandLine(); // 如 Optional[.../java -Dfile.encoding=UTF-8 Main]
|
|
|
|
|
+info.startInstant(); // 启动时间
|
|
|
|
|
+info.totalCpuDuration(); // CPU 累计耗时
|
|
|
|
|
+info.user(); // 所属用户
|
|
|
|
|
+handle.supportsNormalTermination(); // Linux: true,Windows: false
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 通过 PID 解析句柄 —— `resolveByPid()`
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+Optional<ProcessHandle> child = ProcessHandle.of(process.pid()); // 存在 → 有值
|
|
|
|
|
+child.isPresent(); // true
|
|
|
|
|
+ProcessHandle.of(99999999L).isPresent(); // 不存在的 PID → false
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 遍历父子进程树 —— `inspectProcessTree()`
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ProcessHandle current = ProcessHandle.current(); // 当前 JVM 句柄
|
|
|
|
|
+current.children().forEach(h -> log.info("{}", h.pid())); // 直接子进程
|
|
|
|
|
+current.descendants().count(); // 所有后代(含间接)
|
|
|
|
|
+current.parent(); // 当前 JVM 的父进程
|
|
|
|
|
+process.toHandle().parent(); // 子进程的父 = 当前 JVM
|
|
|
|
|
+ProcessHandle.allProcesses().count(); // 注意是静态方法:当前用户可见的活跃进程总数
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 通过句柄销毁进程 —— `terminateViaHandle()`
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ProcessHandle handle = process.toHandle();
|
|
|
|
|
+
|
|
|
|
|
+handle.destroy(); // 与 process.destroy() 同义:Linux 发 SIGTERM,Windows 走 TerminateProcess
|
|
|
|
|
+handle.onExit().join(); // 阻塞到进程真正退出(返回 CompletableFuture<ProcessHandle>)
|
|
|
|
|
+handle.isAlive(); // false(已退出)
|
|
|
|
|
+process.exitValue(); // 被 SIGTERM 终止常见 143
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+要点与陷阱:
|
|
|
|
|
+
|
|
|
|
|
+- `ProcessHandle` 与 `Process` 的关系:`Process` 是"已启动子进程"的句柄,管三路流、等待、销毁;
|
|
|
|
|
+ `ProcessHandle` 是 JDK 9 新增的通用进程句柄,**不依赖 ProcessBuilder/Process 对象**也能按 pid 拿到
|
|
|
|
|
+ 任意可访问进程并查询/销毁。`process.toHandle()` 只是两者间的桥。
|
|
|
|
|
+- `ProcessHandle.allProcesses()` 是**静态方法**,必须用接口名调用(`ProcessHandle.allProcesses()`,
|
|
|
|
|
+ 不能写成 `handle.allProcesses()`,否则编译报错);可能受系统权限限制(Linux 非 root 看不到其他用户进程)。
|
|
|
|
|
+- `ProcessHandle.onExit()` 返回 `CompletableFuture<ProcessHandle>`,与 `Process.onExit()` 返回的
|
|
|
|
|
+ `CompletableFuture<Process>` 不同。
|
|
|
|
|
+- `info()` 的字段都是 `Optional`,进程退出后部分字段可能不再可读。
|
|
|
|
|
+- Windows 下 `supportsNormalTermination()` 为 `false`,`destroy()` 行为等同强杀,无 143 这种信号退出码。
|
|
|
|
|
+
|
|
|
|
|
+预期输出(pid、时间、进程总数随环境变化):
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+== 从 Process 获取 ProcessHandle ==
|
|
|
|
|
+[stdout] child: pid=242593
|
|
|
|
|
+[stdout] child: args=[]
|
|
|
|
|
+[stdout] child: sleeping 30s, waiting for parent to inspect
|
|
|
|
|
+[stderr] child: ready for ProcessHandle inspection
|
|
|
|
|
+process.pid() = 242593
|
|
|
|
|
+handle.pid() = 242593
|
|
|
|
|
+pid 一致: true
|
|
|
|
|
+handle.isAlive() = true
|
|
|
|
|
+command() = Optional[/path/to/java]
|
|
|
|
|
+commandLine() = Optional[/path/to/java -Dfile.encoding=UTF-8 Main]
|
|
|
|
|
+startInstant() = Optional[2026-09-11T10:52:25.210Z]
|
|
|
|
|
+totalCpuDuration()= Optional[PT0.05S]
|
|
|
|
|
+user() = Optional[yangyi]
|
|
|
|
|
+supportsNormalTermination() = true
|
|
|
|
|
+== 通过 PID 解析 ProcessHandle ==
|
|
|
|
|
+ProcessHandle.of(242617) 存在: true
|
|
|
|
|
+ProcessHandle.of(99999999) 存在: false
|
|
|
|
|
+== 进程树与 allProcesses ==
|
|
|
|
|
+当前 JVM pid: 242488
|
|
|
|
|
+子进程 PID 242693 在 children() 中: true
|
|
|
|
|
+子进程 parent 存在: true
|
|
|
|
|
+ parent 就是当前 JVM: true
|
|
|
|
|
+descendants() 数量: 1
|
|
|
|
|
+当前用户可见活跃进程总数: 455
|
|
|
|
|
+== 通过句柄销毁进程 ==
|
|
|
|
|
+进程存活: true
|
|
|
|
|
+destroy() 已发起: true
|
|
|
|
|
+进程已退出,handle.isAlive() = false
|
|
|
|
|
+退出码 (process.exitValue()): 143
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 子进程睡眠 30 秒,示例中所有案例点检查完后立即 `destroy()`,不会真的等 30 秒。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
## 常见踩坑小结
|
|
## 常见踩坑小结
|
|
|
|
|
|
|
|
1. 编译/运行子进程的 `javac`、`java` 用 `java.home` 的**绝对路径**,不要裸写命令名。
|
|
1. 编译/运行子进程的 `javac`、`java` 用 `java.home` 的**绝对路径**,不要裸写命令名。
|
|
|
2. `user.dir` 相对的工作目录要跟 `src/main/resources/code/` 下的夹具目录保持一致,否则 `error=2`。
|
|
2. `user.dir` 相对的工作目录要跟 `src/main/resources/code/` 下的夹具目录保持一致,否则 `error=2`。
|
|
|
3. 同步 `lines().forEach(...)` 读子进程输出会阻塞到子进程退出,限时 `waitFor` 前要在后台线程读取。
|
|
3. 同步 `lines().forEach(...)` 读子进程输出会阻塞到子进程退出,限时 `waitFor` 前要在后台线程读取。
|
|
|
4. `waitFor(timeout, unit)` 返回 boolean;超时后取退出码要 `destroy() + waitFor()` 再 `exitValue()`。
|
|
4. `waitFor(timeout, unit)` 返回 boolean;超时后取退出码要 `destroy() + waitFor()` 再 `exitValue()`。
|
|
|
-5. `ProcessBuilder` 不经 shell:参数数组每一项就是一个参数,引号/`$VAR`/通配符/`|` 都不生效,需要时用 `sh -c "..."`。
|
|
|
|
|
|
|
+5. `ProcessBuilder` 不经 shell:参数数组每一项就是一个参数,引号/`$VAR`/通配符/`|` 都不生效,需要时用 `sh -c "..."`。
|
|
|
|
|
+6. `ProcessHandle.allProcesses()` 是**静态方法**:必须 `ProcessHandle.allProcesses()` 调用,不能写成 `handle.allProcesses()`。
|
|
|
|
|
+7. `ProcessHandle.destroy()` 与 `Process.destroy()` 语义相同(Linux SIGTERM),Windows 走 `TerminateProcess`、`supportsNormalTermination()` 为 `false`。
|