我正在编写一个消耗大量内存的缓存应用程序。
希望我能够很好地管理自己的记忆,但我只想到了什么 如果我的内存不足就要做。
如果调用甚至分配一个简单的对象失败,那么即使是syslog调用也是如此 也会失败?
编辑:好的,或许我应该澄清这个问题。如果malloc或new返回NULL或0L值,那么它实质上意味着调用失败,并且由于某种原因它无法为您提供内存。那么,在那种情况下做什么是明智的事情呢?EDIT2:我刚刚意识到对“new”的调用会引发异常。这可以在更高的水平上捕获,所以我可以优雅地进一步退出。此时,甚至可以根据释放的内存量进行恢复。至少我应该在那一点上希望能够记录一些东西。因此,虽然我已经看到在新的之后检查指针值的代码,但这是不必要的。在C语言中,您应该检查malloc的返回值。
答案 0 :(得分:18)
这个问题不是对过度使用的内存做出假设吗?
即,内存不足的情况可能无法恢复!即使您没有剩余内存,在程序尝试使用内存之前,对malloc
和其他分配器的调用仍可能成功。然后, BAM!,一些进程被内核杀死以满足内存负载。
答案 1 :(得分:17)
好吧,如果您遇到无法分配内存的情况,那么您将获得std::bad_alloc
例外。该异常导致程序堆栈被解除。很可能,应用程序逻辑的内部循环不会处理内存不足的情况,只有更高级别的应用程序应该这样做。因为堆栈正在解开,所以很大一部分内存将被释放 - 实际上应该是你的程序使用的几乎所有内存。
一个例外是当你要求一个非常大(例如几百MB)的内存块时,这是无法满足的。但是,当发生这种情况时,通常会留下足够小的内存块,这样您就可以优雅地处理故障。
堆栈展开是你的朋友;)
编辑:刚刚意识到问题也是用C标记的 - 如果是这种情况,那么当发现内存条件不足时,你应该让你的函数手动释放其内部结构;不这样做就是内存泄漏。
EDIT2:示例:
#include <iostream>
#include <vector>
void DoStuff()
{
std::vector<int> data;
//insert a whole crapload of stuff into data here.
//Assume std::vector::push_back does the actual throwing
//i.e. data.resize(SOME_LARGE_VALUE_HERE);
}
int main()
{
try
{
DoStuff();
return 0;
}
catch (const std::bad_alloc& ex)
{ //Observe that the local variable `data` no longer exists here.
std::cerr << "Oops. Looks like you need to use a 64 bit system (or "
"get a bigger hard disk) for that calculation!";
return -1;
}
}
EDIT3:好的,根据评论者的说法,有些系统在这方面没有遵循标准。另一方面,在这样的系统上,无论如何你都将成为SOL,所以我不明白他们为什么值得讨论。但是,如果您 在这样的平台上,则需要牢记这一点。
答案 2 :(得分:6)
我在Linux上没有任何特定的经验,但我花了很多时间在游戏机上的视频游戏中工作,其中内存耗尽,以及基于Windows的工具。
在现代操作系统上,您最有可能耗尽地址空间。因此,内存耗尽基本上是不可能的。因此,只需在启动时分配一个大缓冲区或缓冲区,以便保存您需要的所有数据,同时为操作系统留下少量数据。将随机垃圾写入这些区域可能是一个好主意,以迫使操作系统实际将内存分配给您的进程。如果你的过程幸存下来,试图使用它所要求的每个字节,那么现在有一些支持保留给所有这些东西,所以现在你是金色的。
写入/窃取您自己的内存管理器,并指示它从这些缓冲区分配。然后,在您的应用中始终如一地使用它,或利用gcc的--wrap
选项正确转发malloc
和朋友的来电。如果您使用任何无法定向的图书馆来打电话给您的记忆管理员,那么他们就会把它们弄得一团糟,因为它们会妨碍您。缺乏可覆盖的内存管理调用是更深层次问题的证据;如果没有这个特定的组件,你会更好。 (注意:即使您使用--wrap
,相信我,这仍然是一个问题的证据!生命太短暂,无法使用那些不会让您的内存管理过载的库!)
一旦你的内存耗尽,好吧,你已经搞砸了,但是你还没有获得之前空闲的空间,所以如果释放一些你要求的内存太难了你可以care)调用系统调用将消息写入系统日志然后终止,或者其他什么。只要确保避免调用C库,因为他们可能会在你最不期望的时候尝试分配一些内存 - 使用具有虚拟化地址空间的系统的程序员因这种事情而臭名昭着 - 这就是这首先引起了这个问题。
这种方法可能听起来像是痛苦的屁股。嗯......是的。但它很简单,值得投入一些努力。我认为有一个关于此的Kernighan和/或Ritche引用。
答案 3 :(得分:4)
如果您的应用程序可能会分配大量内存并且存在达到每个进程或VM限制的风险,那么等到分配实际失败是一个很难恢复的情况。当malloc
返回NULL
或new
投掷std::bad_alloc
时,事情可能太过于无法可靠地恢复。根据您的恢复策略,许多操作本身可能仍需要堆分配,因此您必须非常小心可以依赖的例程。
您可能希望考虑的另一个策略是查询操作系统并监控可用内存,主动管理您的分配。这样,如果您知道它可能会失败,您可以避免分配大块,从而有更好的恢复机会。
此外,根据您的内存使用模式,使用自定义分配器可能会提供比标准内置malloc
更好的结果。例如,某些分配模式实际上会随着时间推移导致内存碎片,因此即使您有空闲内存,堆竞技场中的可用块也可能没有正确大小的可用块。一个很好的例子是Firefox,它切换到dmalloc
并且内存效率大大提高。
答案 4 :(得分:2)
我不认为抓住malloc
或new
的失败会在你的情况下获得更多。 linux
通过malloc
在mmap
中分配大量虚拟页面。通过这种方式,您可能会发现自己分配的虚拟内存比实际更多(实际+交换)。
当你写入第一页没有任何交换位置的程序时,程序只会失败很久以后会出现段错误(SIGSEGV)。理论上,您可以通过编写信号处理程序然后弄脏您分配的所有页面来测试此类情况。
但通常这也无济于事,因为你的应用程序早在那之前就会处于非常糟糕的状态:不断交换,用你的硬盘进行机械计算......
答案 5 :(得分:1)
如前所述,耗尽内存意味着所有赌注都已关闭。恕我直言,处理这种情况的最佳方法是优雅地失败(而不是简单地崩溃!)。您的缓存可以在实例化时分配合理数量的内存。此内存的大小等于释放时允许程序合理终止的数量。当您的缓存检测到内存变低时,它应释放此内存并启动正常关闭。
答案 6 :(得分:1)
在低内存条件下,对syslog的写入可能会失败:如果不查看相关函数的源代码,就无法知道每个平台。例如,他们可能需要动态内存来格式化传入的字符串。
然而,在内存不足之前很久,您将开始将内容分页到磁盘。当发生这种情况时,您可以忘记缓存带来的任何性能优势。
就个人而言,我对Varnish背后的设计深信不疑:操作系统提供解决许多相关问题的服务,使用这些服务(次要编辑)是有意义的:
那么Squid精心设计的内存管理会发生什么呢?它会与内核精心设计的内存管理斗争......
Squid在RAM中创建一个HTTP对象,并在创建后快速使用一段时间。然后经过一段时间后,它不再受到点击,内核会注意到这一点。然后有人试图从内核获取内存,内核决定将那些未使用的内存页面推送到交换空间,并对程序实际使用的某些数据更合理地使用(cache-RAM)。然而,这是在没有Squid知道的情况下完成的。 Squid仍然认为这些http对象在RAM中,并且它们将是它试图访问它们的第二个,但在此之前,RAM用于生产的东西。 ...
一段时间后,Squid还会注意到这些对象未被使用,并且它决定将它们移动到磁盘,因此RAM可用于更繁忙的数据。所以Squid出去,创建一个文件,然后将http对象写入文件。
这里我们切换到高速摄像头:Squid调用write(2),它给出的地址是“虚拟地址”,内核将其标记为“不在家”。 ...
内核试图找到一个空闲页面,如果没有,它会从某个地方拿一点用过的页面,可能是另一个小用过的Squid对象,把它写入磁盘上的分页...空间(“交换”区域“)当写入完成时,它将从寻呼池中的另一个位置读取它”分页“到现在未使用的RAM页面的数据,修复分页表,并重试失败的指令。 ...
所以现在Squid将对象放在RAM的页面中并将其写入磁盘两个位置:操作系统的调页空间中有一个副本,文件系统中有一个副本。 ...
以下是Varnish的表现:
Varnish分配一些虚拟内存,它告诉操作系统用磁盘文件中的空间来备份这个内存。当需要将对象发送到客户端时,它只是引用该虚拟内存并将其余内容留给内核。
如果/当内核决定需要将RAM用于其他内容时,页面将被写入后备文件并且RAM页面将在其他地方重复使用。
当Varnish下次参考虚拟内存时,操作系统会找到一个RAM页面,可能会释放一个,然后从后备文件中读取内容。
就是这样。 Varnish并没有真正尝试控制缓存在RAM中的内容,什么不是,内核有代码和硬件支持来做好工作,并且它做得很好。
您可能根本不需要编写缓存代码。
答案 7 :(得分:1)
我正在编写一个消耗大量内存的缓存应用程序。 希望我能够很好地管理自己的记忆,但是我只是在考虑如果我的内存不足会怎么做。
如果您正在编写应该24/7/365运行的deamon,那么您不应该使用动态内存管理:事先预先分配所有内存并使用某些slab allocator /内存池机制对其进行管理。这也将再次保护你的堆碎片。
如果调用甚至分配一个简单的对象失败,即使是syslog调用也可能会失败?
不应该。这是部分原因syslog
作为系统调用存在:该应用程序可以报告独立于其内部状态的错误。
如果malloc或new返回NULL或0L值,那么它实质上意味着调用失败,并且由于某种原因它无法为您提供内存。那么,在那种情况下做什么是明智的事情呢?
我通常会在这种情况下尝试正确处理错误情况,并应用常规错误处理规则。如果在初始化期间发生错误 - 以错误终止,可能是配置错误。如果在请求处理期间发生错误 - 请求因内存不足错误而失败。
对于普通堆内存,malloc()
返回0
通常意味着:
您已经耗尽了堆,除非您的应用程序释放一些内存,否则malloc()
将无法成功。
错误的分配大小:在计算块大小时混合有符号和无符号类型是很常见的编码错误。如果大小最终错误地为负数,则传递到预期malloc()
的{{1}},它会变得非常大。
因此在某种意义上,size_t
生成核心文件也没有错,以后可以对其进行分析,以了解abort()
返回malloc()
的原因。虽然我更喜欢(1)在错误消息中包含尝试的分配大小,(2)尝试继续进行。如果应用程序因为其他内存问题(*)而崩溃,那么无论如何都会产生核心文件。
(*)根据我使用动态内存管理软件恢复0
错误的经验,我发现malloc()
通常不能可靠地返回malloc()
。返回0
的第一次尝试后跟一个成功的0
返回有效指针。但是首先访问指向的内存会使应用程序崩溃。这是我在Linux和HP-UX上的经验 - 我也在Solaris 10上看到了类似的模式。该行为并非Linux独有。据我所知,使应用程序100%适应内存问题的唯一方法是提前预先分配所有内存。对于关键任务,安全,生命支持和运营商级应用程序而言,这是强制性的 - 在初始化阶段不允许动态内存管理。
答案 8 :(得分:-1)
我不知道为什么许多明智的答案都被否决了。在大多数服务器环境中,内存不足意味着您在某处出现泄漏,并且“释放一些内存并尝试继续”是没有意义的。 C ++,特别是标准库的本质是它需要一直分配。如果幸运的话,您可以释放一些内存并执行干净关闭,或者至少发出警告。
然而,除非失败的分配是一个巨大的分配,并且仍有可用于“正常”事物的内存,否则您将无法做任何事情的可能性更大。
Dan Bernstein是我所知道的为数不多的可以实现在内存受限情况下运行的服务器软件的人之一。
对于我们大多数人来说,我们应该设计我们的软件,当它由于内存不足错误而退出时,它会使事物处于有用的状态。
除非你是某种脑外科医生,否则没有其他事可做。
此外,你甚至不会获取一个std :: bad_alloc或类似的东西,你只需要一个指针来回到你的malloc / new,并且只有你死了实际上试着触摸所有的记忆。这可以通过关闭操作系统中的过度使用来防止,但仍然可以。
当你触摸内核希望你不会出现的内存时,不要指望能够处理SIGSEGV。我不太确定这是如何在windows方面工作的,但我打赌他们也过度使用。
总而言之,这不是C ++的强项之一。