Sfoglia il codice sorgente

doc: 补充 ProcessHandle 章节(process.md 大纲 + doc.md 详细教程,含 API 表/预期输出/踩坑)

yangyi 1 settimana fa
parent
commit
a2933a72d2
2 ha cambiato i file con 117 aggiunte e 3 eliminazioni
  1. 110 2
      doc.md
  2. 7 1
      process.md

+ 110 - 2
doc.md

@@ -1,6 +1,6 @@
 # JAVA Process 使用教程
 
-本教程通过 6 个可运行的示例,讲解 `ProcessBuilder` / `Process` 的核心用法。所有示例都以
+本教程通过 7 个可运行的示例,讲解 `ProcessBuilder` / `Process` 的核心用法。所有示例都以
 `src/main/resources/code/` 下的 `.java` 程序作为子进程,先编译再运行。
 每个**案例点**由独立的 `static` method 演示,在 `main()` 中依次调用。
 
@@ -312,6 +312,7 @@ int exitCode = process.exitValue();  // 被信号终止时通常为 143(SIGTER
 | `destroyForcibly()` | 无 | `Process` | 强制终止(Linux 为 SIGKILL) |
 | `isAlive()` | 无 | `boolean` | 进程是否仍在运行 |
 | `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` 的**绝对路径**,不要裸写命令名。
 2. `user.dir` 相对的工作目录要跟 `src/main/resources/code/` 下的夹具目录保持一致,否则 `error=2`。
 3. 同步 `lines().forEach(...)` 读子进程输出会阻塞到子进程退出,限时 `waitFor` 前要在后台线程读取。
 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`。

+ 7 - 1
process.md

@@ -13,4 +13,10 @@
    > 详细解释API的每个参数和返回值
    - ProcessBuilder
    - Process
-5. 综合运用:管道式调用外部工具
+5. 综合运用:管道式调用外部工具
+6. 使用 ProcessHandle 管理进程(JDK 9+)
+   > 从 Process 获取句柄、查询元信息、遍历进程树、通过句柄销毁
+   - 从 Process 获取 ProcessHandle 并查询信息
+   - 通过 PID 解析句柄
+   - 父子进程树与 allProcesses
+   - 通过句柄销毁进程与 onExit