最直接方法是调用NtQuerySystemInformation并传入SystemHandleInformationEx(值64),需管理员权限及SeDebugPrivilege提权,可高效获取全局句柄总数而无需遍历全部句柄。

Windows 下用 NtQuerySystemInformation 获取句柄总数最直接
Windows 内核不提供公开的、稳定版的 API 直接返回“当前系统全局句柄数”,但 NtQuerySystemInformation 是唯一能拿到接近真实值的接口。它属于未文档化但长期兼容的 NT 内核函数,几乎所有 Windows 版本(Win7–Win11)都支持,且被任务管理器、Process Explorer 等工具实际调用。
关键点在于传入 SystemHandleInformation(值为 16)或更高效的 SystemHandleInformationEx(值为 64,Win10 1809+),后者只返回统计摘要,不拉取全部句柄表,开销极低:
// 示例:仅获取统计总数(无需遍历)
SYSTEM_HANDLE_INFORMATION_EX* info = nullptr;
ULONG len = 0;
NTSTATUS status = NtQuerySystemInformation(
SystemHandleInformationEx, // 注意不是 SystemHandleInformation
nullptr, 0, &len
);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
info = (SYSTEM_HANDLE_INFORMATION_EX*)malloc(len);
NtQuerySystemInformation(SystemHandleInformationEx, info, len, &len);
printf("Total handles: %llu\n", info->NumberOfHandles); // 这就是你要的全局总数
}别用 SystemHandleInformation 去硬算——它返回每个进程的每个句柄条目,几万条数据全拷贝下来再计数,延迟高、内存涨、还容易因权限不足失败。
为什么 GetProcessHandleCount 不满足“全局”监控需求
GetProcessHandleCount 只返回**当前进程**的句柄数,和系统级监控完全无关。它底层调用的是 NtQueryInformationProcess + ProcessHandleCount 类型,作用域严格限定在调用者进程内。
立即学习“C++免费学习笔记(深入)”;
常见误用场景:
- 在服务进程中调用它,误以为能反映系统负载——其实只反映该服务自己泄漏了多少句柄
- 用它替代系统级告警阈值判断,结果句柄爆满时毫无预警(因为系统总句柄已超 1500 万,而你的进程才用了 2000 个)
真正需要全局视角时,必须绕过 Win32 层,进 NT 层拿 SystemHandleInformationEx。
权限与稳定性:管理员权限不是可选,而是必须
调用 NtQuerySystemInformation 获取句柄统计,要求进程至少拥有 SeDebugPrivilege 权限。普通用户权限下会直接返回 STATUS_ACCESS_DENIED,连错误码都拿不到完整信息。
实操要点:
- 调用前必须用
OpenProcessToken+LookupPrivilegeValue+AdjustTokenPrivileges提权,缺一不可 - 提权失败时不要静默跳过——应明确报错
"Missing SeDebugPrivilege, cannot query system handle count" - Win11 22H2+ 启用 HVCI(基于虚拟化的安全)后,部分驱动/沙箱环境会拦截该调用,需降级到
SystemHandleInformation并捕获STATUS_INVALID_INFO_CLASS
没有提权就跑,大概率看到 0 或崩溃,不是代码写错了,是权限卡死了。
句柄总数暴涨 ≠ 内存泄漏,但一定是资源调度异常信号
Windows 全局句柄上限理论约 2^24(1677 万),但实际稳定运行通常不超过 800 万。当监控发现 NumberOfHandles 在 5 分钟内增长超 30 万,优先排查三类问题:
- 某进程反复
CreateEvent/CreateMutex后忘记CloseHandle(尤其在循环中) - 驱动层句柄泄漏:如某些 USB 设备驱动在热插拔时未清理
FILE_OBJECT关联句柄 - 远程桌面会话残留:断开 RDP 后,session 中的子进程未彻底退出,句柄滞留数小时不释放
注意:句柄本身只占内核对象头(约 0x40 字节),不等于内存爆炸,但它直接关联 ObReferenceObjectByHandle 调用频次,会影响内核调度延迟。监控曲线比绝对值更有意义——平稳上升可接受,阶梯式跃升必有问题。


















