You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何处于挂起状态的monitor程序内存占用持续增长?

监控程序挂起时内存持续增长的原因分析

问题背景

开发了一个C++后台监控程序monitor,功能是监控目标程序target,当target退出时自动重启它。测试阶段用notepad.exe作为目标程序,实际场景中target是一款联网GUI应用,正常情况下不会主动退出,因此monitor会长期处于挂起等待状态。但观察发现,即使程序处于挂起状态,其内存占用仍呈长期增长趋势。

程序未显式使用new()或malloc()分配内存,仅在调用CreateToolhelp32Snapshot()和OpenProcess()时涉及系统资源分配,且已通过CloseHandle()释放对应句柄,理论上不应出现内存泄漏。

监控程序代码

#include <windows.h>
#include <tlhelp32.h>
#include <Process.h>

void BindToProcess();

const WCHAR PATH[] = L"C:\\Windows\\System32\\notepad.exe";
const WCHAR EXE[] = L"notepad.exe";

int CALLBACK WinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nShowCmd)
{
    do
    {
        BindToProcess();
        _wspawnl(_P_WAIT, PATH, PATH, NULL);
    } while (TRUE);
    return 0;
}

void BindToProcess()
{
    HANDLE hProcessSnap;
    PROCESSENTRY32 pe32;

    // Take a snapshot of all processes in the system.
    hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE) return;

    // Set the size of the structure before using it.
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // Retrieve information about the first process,
    if (!Process32First(hProcessSnap, &pe32))
    {
        CloseHandle(hProcessSnap);
        return;
    }

    // Now walk the snapshot of processes, and search for target
    do
    {
        const int len = (int) wcsnlen_s(EXE, 10);
        if (CompareStringW(0, 0, pe32.szExeFile, len, EXE, len) == CSTR_EQUAL)
        {
            HANDLE handle[1];
            handle[0] = OpenProcess(SYNCHRONIZE, FALSE, pe32.th32ProcessID);
            WaitForMultipleObjects(1, handle, TRUE, INFINITE);
            CloseHandle(handle[0]);
            CloseHandle(hProcessSnap);
            return;
        }
    } while (Process32Next(hProcessSnap, &pe32));
    CloseHandle(hProcessSnap);
    return;
}

内存增长的核心原因分析

1. GUI程序缺少消息循环,导致消息队列堆积

程序使用WinMain作为入口,表明它属于Windows GUI子系统。Windows会为GUI程序自动创建消息队列,系统会定期向程序发送各类消息(如WM_NULL、系统通知等),即使程序没有创建窗口。

而当前程序在WaitForMultipleObjects(...)处长期挂起,完全没有处理消息的逻辑,导致系统发送的消息不断堆积在消息队列中。每一条未处理的消息都会占用内存空间,随着时间推移,内存占用自然会持续增长。

2. Debug编译版本的CRT内存调试机制(次要原因)

如果程序是用Debug模式编译的,Visual Studio的C运行时库(CRT)会为每一次内存分配添加额外的调试跟踪信息。即使调用了CloseHandle()释放系统资源,CRT的堆管理器可能不会立即将内存归还给操作系统,而是保留在进程的堆空间中用于后续复用,这会导致内存占用看起来持续上升。这种情况在Release模式下会显著缓解。

3. 系统工作集的波动(次要原因)

Windows系统会根据进程的运行情况动态调整其工作集(Working Set)大小,可能会预分配一些内存页面或保留常用页面在内存中,导致内存占用出现小幅波动。但这种波动通常是双向的,不会呈现长期持续增长的趋势,因此不是核心原因。

解决方案

方案一:改为控制台子系统程序

将程序入口从WinMain改为main,编译时指定控制台子系统。这样Windows不会为程序创建GUI消息队列,从根源上避免消息堆积问题。

方案二:在等待时处理消息

将WaitForMultipleObjects替换为MsgWaitForMultipleObjects,在等待目标进程退出的同时,处理系统发送的消息。示例修改如下:

// 替换BindToProcess中的WaitForMultipleObjects代码段
HANDLE handle[1];
handle[0] = OpenProcess(SYNCHRONIZE, FALSE, pe32.th32ProcessID);

// 循环等待,同时处理消息
DWORD waitResult;
do {
    waitResult = MsgWaitForMultipleObjects(1, handle, FALSE, INFINITE, QS_ALLINPUT);
    if (waitResult == WAIT_OBJECT_0 + 1) {
        // 处理所有待处理的消息
        MSG msg;
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
} while (waitResult != WAIT_OBJECT_0);

CloseHandle(handle[0]);
CloseHandle(hProcessSnap);
return;

这样既能保持对目标进程的监控,又能及时处理系统消息,避免消息堆积导致的内存增长。

内容的提问来源于stack exchange,提问作者Kalle Svensson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:10:34