堆栈内存未释放

Stack memory not released

本文关键字:释放 内存 堆栈      更新时间:2023-10-16

我有以下循环,它从这里的实现中弹出我拥有的C++并发队列。 https://juanchopanzacpp.wordpress.com/2013/02/26/concurrent-queue-c11/

while (!interrupted)
{
    pxData data = queue->pop(); 
    if (data.value == -1)
    { 
        break; // exit loop on terminating condition
     }
    usleep(7000); // stub to simulate processing
}

我正在使用 CentOS7 中的系统监视器查看内存历史记录。从队列中读取值后,我正在尝试释放队列占用的内存。但是,随着以下 while 循环运行,我没有看到内存使用量下降。我已经验证了队列长度确实下降了。

但是,当遇到 -1 并且循环退出时,它确实会下降。(程序仍在运行(但我不能有这个,因为usleep在哪里,我想做一些密集的处理。

:为什么数据占用的内存没有释放?(根据系统监视器(当变量超出范围时,堆栈分配的内存不是应该释放吗?

结构定义如下,并在程序开始时填充。

typedef struct pxData
{
  float value; // -1 value terminates the loop
  float x, y, z;
  std::complex<float> valueData[65536];
} pxData;

它填充了~10000 pxData,大致转换为5GB。系统只有 ~8GB。因此,释放内存以在系统中执行其他处理非常重要。

这里有一些事情在起作用。

虚拟内存

首先,您需要了解,仅仅因为您的程序"使用"了 5 GB 的内存并不意味着只剩下 3 GB 的 RAM 留给其他程序。 虚拟内存意味着这 5 GB 可能只是 1 GB 的实际"驻留"数据,另外 4 GB 实际上可能在磁盘上而不是在 RAM 中。 因此,在查看程序时,查看"驻留集大小"而不是"虚拟大小"非常重要。 请注意,如果您的系统实际上内存不足,操作系统可能会通过"分页"某些程序的某些内存来缩小某些程序的 RSS。 因此,不要太担心系统监视器中出现"5 GB" - 如果您有实际,具体的性能问题,请担心。

堆分配

第二个方面是为什么您的虚拟大小不会随着您从队列中删除项目而减小。 我们可以猜测,您将这些元素放入队列中,方法是逐个创建mallocnew,然后将它们推到队列的后面。 这意味着您分配的第一个元素将首先从队列中出来。 这反过来意味着,当您耗尽了 90% 的队列时,您的内存分配可能如下所示:

[program|------------------unused-------------------|pxData]

这里的问题是,在现实世界中,仅仅因为您freedelete某些东西并不意味着操作系统会立即回收该内存。 事实上,它可能无法回收任何未使用的跨度,除非它们位于"末端"(即最近分配(。 由于C++没有垃圾回收功能,并且未经您的同意不能在内存中移动项目,因此您最终会在程序的虚拟内存中出现这个大"漏洞"。 该洞将用于满足未来的内存分配请求,但如果你没有,它就坐在那里,直到队列完全清空:

[program|------------------unused--------------------------]

然后,系统能够缩小虚拟地址空间:

[program]

这会带您回到起点。

解决 方案

如果要"修复"此问题,一种选择是"反向"分配内存,即将最后分配的项目放在队列的前面。

另一种选择是通过mmap为队列分配元素,例如Linux会自动为"大"分配执行此操作。 您可以通过使用 M_MMAP_THRESHOLD 调用 mallopt(3) 并将其设置为比结构大小小一点来更改此阈值。 这使得分配彼此独立,因此操作系统可以单独回收它们。 这种技术甚至可以应用于现有程序而无需重新编译,因此如果您需要在无法修改的程序中解决此问题,则通常很有用。

C++实现会调用一些operator delete来释放动态分配的(使用一些operator new(内存。在几个C++标准库中,new调用mallocdelete调用free

(我专注于Linux的观点,但原理在其他操作系统上是相似的(

但是,虽然malloc(或::operator new(有时会通过系统调用来要求操作系统内核增加内存,如mmap(2(,free(或::operator delete(通常只是简单地将释放的内存区域标记为重新可用于将来malloc(或new(的调用。

所以从内核的角度来看 (例如/proc/, 参见 proc(5(...( 虚拟地址空间没有改变, 内存仍然被消耗, 即使在应用程序内部它被标记为 "freed" 并且将在将来的某个分配中重用 (通过将来对 mallocnew 的调用(

大多数标准容器C++内部都使用堆数据。特别是您的本地(堆栈分配(std::mapstd::vector(或std::deque(变量调用内部数据的newdelete


顺便说一句,我觉得你的声明很奇怪。除非每个struct pxData都有 65536 个已用valueData插槽,否则我建议使用一些 std::vector,所以有

  std::vector<std::complex<float>> valueData;

并相应地改进您的代码。您可能需要做一些valueData.reserve(somesize);和/或valueData.resize(somesize);和/或valueData.push_back(somecomplexnumber);等。