【问题标题】:C++ _popen() windows leaks paged pool memoryC++ _popen() windows 泄漏分页池内存
【发布时间】:2022-01-30 16:35:10
【问题描述】:

主应用程序在 Windows 服务中运行,该进程启动其他 c++ 控制台进程,但所有控制台模式都被隐藏,即父进程是 Windows 服务,子进程是非控制台应用程序。

在客户系统 windows server 2016 上调用 _popen() 期间观察到系统的分页池内存正在增加。该应用程序在我们的实验室系统相同的操作系统上运行干净。

从 Windows 性能工具 xperf,捕获日志并检查调用堆栈。 附上图片供参考。

void CMachine::GetJavaVersion()
{
m_stJavaVersion.m_strName = " Java version";

CPUChar strVersion[64] = { 0 };
BOOL bFound = CheckJREVersion(strVersion, 64);

BYTE bytColorSt = RED;
string strRemark;

FILE *fp = NULL;
char version[130] = { 0 };
BOOL bFoundVersion = FALSE;
fp = _popen("java -version 2>&1", "r");
while (fp && fgets(version, sizeof version, fp))
{
    string strTmp = version;
    if (strTmp.find("version") != string::npos)
    {
        bFoundVersion = TRUE;
        break;
    }
}
if(fp) _pclose(fp);

....

PoolMon 跟踪

内存:33401164K 可用:30057324K PageFlts:92362 InRam Krnl:20212K P:776328K 提交:3228052K 限制:37595468K 峰值:4747992K 池 N:182820K P:782568K 系统池信息 标记类型 Allocs 释放每个 Alloc 的差异字节

托克传呼 10546816 (390) 10319712 (382) 227104 324868080 (11392) 1430
CM31 分页 42886 ( 0) 20849 ( 0) 22037 101154816 ( 0) 4590
座位 44678436 (1662) 43769798 (1630) 908638 87253680 (3072) 96
QINi 已分页 234 ( 0) 1 ( 0) 233 60293216 ( 0) 258769
MmSt 分页 2683066 (79) 2670922 (83) 12144 27223856 (3312) 2241

PoolMon

【问题讨论】:

  • 通常涉及资源获取的内存泄漏(例如对_popen的调用)来自于未能释放该资源(即对_pclose的相应调用)。您是否尝试将代码简化为只调用 _popen/_pclose 的小程序?
  • 我发现的另一件事是,一些错误仅在您大规模运行应用程序时才会出现。当它在您的实验室中“运行干净”时,输入数据样本与客户的完整数据相比有多大?
  • _popen() 生成命令提示符以在 30 秒的计时器上获取已安装的 Java 版本信息。同样从日志中观察到 FILE 指针对关闭是有效的
  • 不知道Java子进程在客户机器上是不是从来不退出?这可能会导致您的 fgets() 调用永远不会返回,因此 _pclose() 永远不会被调用。也许客户的 Java.exe 以某种方式损坏,或者另一个名为 Java.exe 的程序位于命令路径中并被执行?
  • 顺便说一句:如果“版本”的输出在中间被 130 字符缓冲区剪切,您将找不到它。

标签: c++ windows memory-leaks pool


【解决方案1】:

Eric Lippert 写了关于 benchmark mistakes 的文章。我认为错误#1适用于您的情况:

错误 #1:选择了错误的指标。

为什么要测量“分页池”来确定内存泄漏?

分页内存是换出到磁盘的内存。发生这种情况是因为其他东西需要物理 RAM。需要什么物理 RAM?可能是为了运行您启动的进程。

内存交换到磁盘后,可能需要一段时间才能交换回 RAM。这只会在其他应用程序尝试访问内存时发生 - 如果有的话,可能需要几分钟。

我也倾向于说内存不是在方法调用期间泄漏,而是在方法调用之后泄漏。方法调用后,所有变量都应该被销毁,相关资源应该被释放。


如果你被告知分页池是原因,那么请寻求证据。

在我的 Windows 10 系统上,分页池限制为 17 GB。这可以通过 Process Explorer 在配置了符号的视图/系统信息中显示。

如果您经常运行java -version,以至于它会泄漏 17 GB 的内核内存,那么就存在严重错误。当然,会有一个管道或其他东西将输出从 Java 重定向到您的应用程序,以便您可以读取流。还会有其他内核对象,如进程、线程等。

即使每次调用都有 1 kB 的内核内存泄漏,您也需要调用 1700 万次才能耗尽分页池。如果是这种情况,也许您应该考虑缓存结果。服务器管理员不太可能在几天内安装和卸载 Java 1700 万次。

要监控分页池,您可以尝试Poolmon/p /P command line parameters。 Poolmon 是WDK 的一部分。


代码中的问题:

您的代码至少有 2 个问题:

  1. 如果“版本”从未出现在输出中,您的代码可能会在无限循环中运行。怎么会这样?这不太可能,但如果我将我的 HelloWorld.exe 重命名为 java.exe,它可以。

  2. 如果“version”出现在输出中,但意外地“ver”在第一个缓冲区中,而“sion”在第二个缓冲区中,你永远不会发现它实际上在那里。您的代码可能会陷入无限循环。

【讨论】:

  • 必须假设是客户注意到了这一点并打电话投诉。现在,您如何向客户解释这一点?
  • @Spencer:客户一直使用错误的指标。最好的例子是使用 Windows 任务管理器并抱怨“工作集大小”过多。我的建议是:移除 RAM,它会变低。
  • @Spencer:通常最好举一个完全不涉及您的应用程序的示例,例如运行 Word 或以前未运行过的任何其他应用程序。这是否也会增加页面池?看到了吗?
  • 或者只是亲吻:说“别担心。看 X。”
  • 当系统(Windows Server 2016)空闲或未安装我们的应用程序时,系统内存是稳定的。但是在安装和运行我们的应用程序的同一个系统中,内存会定期泄漏,即使用计时器,最后服务器会重新启动,因为没有内存可供应用程序运行。
猜你喜欢
  • 2010-12-15
  • 1970-01-01
  • 2012-04-16
  • 1970-01-01
  • 2014-05-03
  • 2010-12-21
  • 2016-01-27
  • 2010-11-11
  • 2017-02-18
相关资源
最近更新 更多