bigywater.tech
Java · 402

ProcessHandle:观察和管理子进程

ProcessHandle 让 Java 能观察和管理任意进程,不只是自己启动的子进程。这篇用真实的进程树量出它能读到什么、怎么遍历、通知有多快,以及 destroy 与 destroyForcibly 的真实含义。

454 / 742
allProcesses() 里能读到 info().command 的进程数(没有权限的进程为空)
4 / 4
只 destroyForcibly 父 shell 之后仍然存活的后代数
137
忽略 SIGTERM 的进程被 destroyForcibly 之后 waitFor 的退出码(destroy() 之后 1.5 s 仍然存活)

当前进程与 info

实测 · 本机执行输出
  pid = 26574,isAlive = true,supportsNormalTermination = true
  info.command = …/java
  info.commandLine = /opt/homebrew/opt/openjdk@21/bin/java -cp out Main info
  info.arguments = [-cp, out, Main, info]
  info.startInstant 有值 = true,info.totalCpuDuration = 31 ms,info.user = w
  parent = pid 26561 命令 Python 参数 exp/j262.py
  对当前进程 destroy():IllegalStateException:destroy of current process not allowed
  allProcesses().count() = 742,用时 1 ms
  对其中每个进程调用 info():有 command 的 454 / 742 个,用时 21 ms(没有权限的进程 command 为空)

children 与 descendants

实测 · 本机执行输出
  父 shell:pid 26576 命令 bash 参数 -c sleep 61 & sleep 62 & sh -c 'sleep 63 & wait' & wait
  children() = [pid 26579 命令 bash 参数 -c sleep 63 & wait, pid 26578 命令 sleep 参数 62, pid 26577 命令 sleep 参数 61]
  descendants() 共 4 个:[pid 26579 bash -c sleep 63 & wait, pid 26578 sleep 62, pid 26577 sleep 61, pid 26580 sleep 63]
  destroyForcibly 只杀父 shell 之后:父 isAlive = false;原来的 4 个后代里仍然存活 4 个;它们现在的 parent = [pid 1, pid 26579]
  逐个 destroyForcibly 之后仍然存活 0 个

onExit

实测 · 本机执行输出
  子进程 sleep 1:onExit() 在 993 ms 时完成,isAlive = false,Process.exitValue = 0
  非子进程(由已退出的 shell 派生,pid 26587):isAlive = true,parent = pid 1
  它的 onExit() 完成:从调用起 1019 ms(sleep 1 剩余约 1000 ms 减去派生后经过的时间)
  10 次 sleep 0.3 的 onExit 延迟(毫秒,排序):[303, 304, 304, 307, 308, 310, 311, 313, 313, 313],中位数 310
  进程已退出后 ProcessHandle.of(pid) = Optional.empty

destroy 与 destroyForcibly

实测 · 本机执行输出
  忽略 SIGTERM 的进程:destroy() 返回 true(只表示请求已发出),1.5 s 后 isAlive = true
  destroyForcibly() 返回 true,waitFor 返回退出码 137(= 128 + 信号 9),用时 1 ms,isAlive = false
  普通 sleep 30 被 destroy():退出码 143(= 128 + 15,SIGTERM)
  对已退出的进程再次 destroy():false,destroyForcibly():false
  对 pid 1(launchd)destroy():false(没有权限)
  清理检查:实验结束后 pgrep 还能找到的 sleep 61/62/63 进程 0 个

分步演示

ProcessHandle
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,macOS arm64(info() 的字段可用性与权限是平台相关的,Linux 与 Windows 不同);进程是 sleep、sh 与 bash 小程序
    • 没有验证:Linux 的 /proc 实现与权限差异、Windows 上 destroy() 的语义(没有 SIGTERM)、pid 回收(onExit 对 pid 被复用的进程)、容器里的 PID namespace、ProcessHandle 与 ProcessBuilder.Redirect 的结合(下一篇)
    京ICP备2026067502号-1京公网安备11010502063487号