ProcessHandle / StackWalker / 进程与栈源码
概述
JDK 9 为进程管理与栈访问引入了两个接口化 API:
ProcessHandle:以对象方式访问本机进程——查询 pid、枚举全部进程、读取进程信息(命令行、用户、CPU 时长)、异步等待进程退出。StackWalker:按需、流式地访问当前线程的栈帧,替代Throwable.getStackTrace()的全量快照方式,支持只取调用者类、懒加载帧信息等精细控制。
两者都采用"接口 + 平台相关实现类"的结构:ProcessHandleImpl 依赖 native 枚举(Linux /proc、Windows Toolhelp),StackWalker 依赖 JVM 的栈遍历原语。本文基于 OpenJDK 21 源码拆解这两条链路。
核心源码解析
① ProcessHandle.current() 的当前进程
java
// ProcessHandle.java
static ProcessHandle current() {
return ProcessHandleImpl.current();
}
// ProcessHandleImpl.java
final class ProcessHandleImpl implements ProcessHandle {
private final long pid; // 进程 id
private final long startTime; // 进程启动时间(区分 pid 复用)
private static final long PID = getCurrentPid0(); // native:当前进程 pid
private static final ProcessHandleImpl CURRENT =
new ProcessHandleImpl(PID, getStartTime0(PID, 0), 0);
static ProcessHandleImpl current() {
return CURRENT; // 单例:当前进程句柄
}
...
private static native long getCurrentPid0();
}getCurrentPid0()是 native:Windows 走GetCurrentProcessId(),Linux 走getpid()。CURRENT是进程启动时创建的静态单例,pid()直接返回该常量,零开销。startTime用于解决 pid 复用问题:进程退出后 pid 可能被新进程占用,isAlive()校验时同时比对 startTime 与当前进程,避免误判。
② ProcessHandle.allProcesses() 的进程枚举
java
static Stream<ProcessHandle> allProcesses() {
return ProcessHandleImpl.allProcesses();
}
// ProcessHandleImpl
static Stream<ProcessHandle> allProcesses() {
long[] pids = nativeProcessList(); // ① OS 级枚举所有 pid
return Arrays.stream(pids)
.mapToObj(pid -> new ProcessHandleImpl(pid, getStartTime0(pid, 0), 0))
.filter(ph -> ph.startTime != 0); // ② 过滤已退出/无权限进程
}nativeProcessList():Linux 枚举/proc目录下的数字目录,Windows 走CreateToolhelp32Snapshot+Process32First/Process32Next遍历。children()/descendants()复用同一枚举:children()枚举全进程后按parent pid == this.pid过滤(Windows 平台在 native 侧直接查父 pid 字段),descendants()递归收集子孙。- 返回
Stream<ProcessHandle>,懒加载:每个ProcessHandle都是轻量对象(仅 pid + startTime),信息类(Info)在调用info()时才获取。
③ ProcessHandle.onExit() 的异步等待
java
// ProcessHandleImpl.onExit()
public CompletableFuture<ProcessHandle> onExit() {
final CompletableFuture<ProcessHandle> cf = new CompletableFuture<>();
ExitCompletion completion = new ExitCompletion(this, cf);
synchronized (processReaper) {
processReaper.add(completion); // 注册到退出监听器
}
return cf;
}- 返回
CompletableFuture<ProcessHandle>:进程退出时该 future 以进程句柄为结果正常完成,进程被强制终止等异常场景同样完成(状态由destroyForcibly决定)。 - 底层机制:JDK 在启动时创建后台退出监视线程(
ProcessHandleImpl的 reaper),持有pid与startTime,循环调用 nativewaitpid(Linux,WNOHANG模式)或轮询isAlive();命中退出即调用completion.run()完成 future。 cf.get()等价于阻塞等待进程退出;配合whenComplete/thenAccept可实现"进程退出后的清理回调",是替代"轮询isAlive()+ 手动休眠"的推荐姿势。
④ ProcessHandle.Info 的信息快照
java
// ProcessHandleImpl.Info 实现 ProcessHandle.Info
private final String command; // 可执行文件路径
private final String commandLine; // 完整命令行
private final String[] arguments; // 参数列表
private final Instant startTime; // 启动时间
private final Duration totalCpuDuration;// 累计 CPU 时长
private final String user; // 启动用户
static Info info0(long pid, Info info) { ... } // native 填充info()调用 nativegetInfo0(pid):Linux 解析/proc/<pid>/stat(startTime、CPU 时长)、/proc/<pid>/cmdline(command + arguments)、/proc/<pid>/status(user);Windows 走QueryFullProcessImageName与GetProcessTimes。- 所有字段以
Optional包装:无权限或平台不支持时返回Optional.empty()(如user()在某些容器环境不可用)。 totalCpuDuration()为累计 CPU 时长(用户态 + 内核态);快照语义——每次info()调用重新获取,非缓存。
⑤ StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE) 的获取
java
public final class StackWalker {
public enum Option {
RETAIN_CLASS_REFERENCE, // 保留 Class 引用(可 getDeclaringClass())
SHOW_HIDDEN_FRAMES, // 显示 JVM 内部隐藏帧
SHOW_REFLECT_FRAMES; // 显示反射/代理帧
}
public static StackWalker getInstance(Option option, Option... moreOptions) {
return getInstance(EnumSet.of(option, moreOptions)); // 位掩码语义:EnumSet 位向量
}
public static StackWalker getInstance(Set<Option> options) {
return new StackWalkerImpl(options, NO_CALLER_CLASS);
}
}Option通过EnumSet存储——内部就是long位掩码(EnumSet.noneOf按枚举个数选择RegularEnumSet的 64 位掩码),每个 Option 占一位。RETAIN_CLASS_REFERENCE:默认情况下栈帧只保留类名(不加载类),开启后StackFrame.getDeclaringClass()可直接返回Class对象——代价是持有类的强引用,可能阻止类卸载。getInstance()(无参)是"零配置"版本:只读类名 + 方法名 + 行号,开销最小;StackWalkerImpl构造时记录callerClass(调用者类),供getCallerClass()使用。FrameBuffer:StackWalkerImpl内部使用ArrayList<StackFrameInfo>作为帧缓冲区,walk 时一次性填充,之后按需访问。
⑥ StackWalker.walk(Stream<StackFrame> → T) 的栈遍历
java
// StackWalkerImpl.walk
public <T> T walk(Function<? super Stream<StackFrame>, ? extends T> function) {
// ① 创建栈流工厂:按 options 决定帧来源
StackStreamFactory ssFactory = new StackStreamFactory(maxDepth, options, accessors);
// ② 收集栈帧到 FrameBuffer
try (AbstractStackWalker<?> stream = ssFactory.makeStackWalker(...)) {
stream.begin(); // JVM 栈遍历起点
T result = function.apply(stream); // ③ 消费帧流
stream.end(); // 关闭(释放 FrameBuffer)
return result;
}
}JVM_GetStackTrace()native:HotSpot 侧为每个线程维护栈快照;begin()时 JVM 一次性把当前线程的栈帧(按 maxDepth 截断)拷贝到 FrameBuffer,之后 Java 侧纯内存访问。- 流生命周期:返回的
Stream<StackFrame>只能在walk的 lambda 内使用,end()后访问抛异常——保证帧缓冲可回收、不逃逸。 getCallerClass()优化:只取第二帧(跳过自身与反射帧),返回Class,比遍历整个栈快得多;getFirstNonSkippedFrame(Class)用于"跳过指定类直到目标帧"。
⑦ StackFrame.getClassName() / getMethodName() / getLineNumber()
java
// jdk.internal.reflect.StackFrameInfo(jdk.internal 包,实现 java.lang.StackWalker.StackFrame)
final class StackFrameInfo implements StackWalker.StackFrame {
private final Object declaringClass; // ClassReference(默认)或 Class(RETAIN_CLASS_REFERENCE)
private final String methodName;
private StackTraceElement ste; // 懒加载:首次访问才构造
...
public String getClassName() {
Class<?> c = declaringClass instanceof Class ? (Class<?>) declaringClass
: ((ClassReference) declaringClass).get();
return c.getName();
}
public String getMethodName() { return methodName; }
public int getLineNumber() { return getStackTraceElement().getLineNumber(); }
}- 字段来源:native 栈遍历时每个帧填充
(declaringClass, methodName, byteCodeIndex);getLineNumber()/getFileName()由StackTraceElement提供——(declaringClass, methodName, fileName, lineNumber)四元组。 - 懒加载:
StackTraceElement只在真正调用getLineNumber()/getFileName()时构造(getStackTraceElement()缓存),只读类名/方法名时零对象分配。 - 与
Throwable.getStackTrace()的区别:后者一次性构造全部帧的StackTraceElement(昂贵的类加载 + 字符串),StackWalker 按需懒加载且可流式短路(如findFirst),性能与内存都更优。
⑧ LiveStackFrame 的局部变量获取
java
// jdk.internal.reflect.LiveStackFrame(实验性,需 SHOW_HIDDEN_FRAMES)
public abstract class LiveStackFrame extends StackFrameInfo {
private Object[] locals; // 局部变量槽
private Object[] monitors; // 当前监视器
private Object[] operands; // 操作数栈快照
...
public abstract Object[] getLocals();
public abstract Object[] getMonitors();
}- 安全点采样:
getLocals()要求线程处于安全点(safepoint),JVM 才能安全读取栈帧中的局部变量表;HotSpot 提供VM.getStackLocals/ native 调用完成对活对象引用的枚举。 - 用途:调试器、可观测性工具、测试框架(如 Jacoco 的栈分析)在 JVM 内采集局部变量;注意这是
jdk.internal.reflect的实验性 API,外部程序不建议直接依赖。 - 前提条件:必须
SHOW_HIDDEN_FRAMES开启,且 JVM 支持栈采样;getMonitors()返回当前帧持有的synchronized监视器对象数组。 - 限制:局部变量中非引用类型(原始类型)需要包装才能观测;JIT 优化后部分局部变量可能不存在(寄存器分配),因此采样结果不保证完整。
总结
| API | 核心类 | 关键机制 |
|---|---|---|
| 进程句柄 | ProcessHandleImpl | native pid 枚举 + 静态单例 + startTime 防 pid 复用 |
| 进程枚举 | allProcesses / children | OS 级列表 + 父 pid 过滤 + 懒加载 Stream |
| 退出等待 | onExit() | CompletableFuture + 后台 reaper 线程 |
| 进程信息 | ProcessHandle.Info | native 读取 /proc(Linux)/ Toolhelp(Windows)快照 |
| 栈遍历 | StackWalkerImpl | EnumSet 选项位掩码 + FrameBuffer + walk 流式消费 |
| 帧信息 | StackFrameInfo | native 填充 + StackTraceElement 懒加载 |
| 栈内省 | LiveStackFrame | 安全点采样局部变量与监视器 |
ProcessHandle 把进程抽象成"可查询、可枚举、可等待"的对象,StackWalker 把栈抽象成"可流式、可懒加载"的数据源。两者的共同设计哲学是接口 + 平台/JVM 实现分离、按需获取、避免全量快照——这也是 JDK 9 之后系统级 API 的标准演进方向。