我有以下代码分配大量数据,如果该数据超过可用内存(此处为32GB),它将引发异常。使用:
bool MyObject::init()
{
char* emergency_memory = new char[32768];
try
{
std::vector<std::vector<MyData> > data_list;
std::vector<MyData> data;
data.resize(1000);
for(size_t i=0; i<1000; i++)
{
data_list.push_back(data);
data_list.push_back(data);
}
}
catch (const std::bad_alloc& e)
{
delete[] emergency_memory;
std::cout << "Data Allocation failed:" << e.what() << std::endl;
return false;
}
return true;
}
从未捕获到异常。该应用程序只是终止或使操作系统崩溃。
我做错了什么?
答案 0 :(得分:2)
您的new
运算符必须从某处获取内存。由于new
是与真实内存没有任何关系的用户空间代码,因此它所能做的就是通过syscall sbrk()
或syscall mmap()
向内核请求内存。内核将通过将一些其他内存页面映射到您进程的虚拟地址空间中来进行响应。
碰巧,内核返回给用户进程的所有内存页面都必须清零。如果跳过此步骤,内核可能会将敏感数据从另一个应用程序或自身泄漏到用户空间进程。
此外,内核总是有一个仅包含零的内存页。因此,只需将一个零页映射到新的地址范围中,它就可以轻松满足任何mmap()
请求。它将这些映射标记为“写时复制”,以便每当您的用户空间进程开始写入该页面时,内核将立即创建零页面的副本。然后内核就会四处寻找另一页内存以支持其承诺。
您看到问题了吗? 内核不需要任何物理内存,直到您的进程实际将其写入内存为止。这称为内存过量使用。当您派生一个进程时,会发生另一种情况。您认为调用fork()
时内核会立即复制您的内存吗?当然不是。它将仅对现有内存页面进行一些COW映射!
(这是一种重要的优化机制:不需要启动许多映射,也不需要额外的内存。这对于fork()
尤其重要:此调用通常紧随exec()
之后,这将立即再次删除COW映射。)
缺点是,内核直到无法兑现自己的承诺,才知道实际需要多少物理内存。这就是为什么内存不足时不能依靠sbrk()
或mmap()
返回错误的原因:直到您写入映射的内存时,您才会耗尽内存记忆。不会从系统调用返回错误代码,这意味着您的new
运算符不知道何时抛出。因此它不会抛出。
所发生的是,当内核意识到其内存不足并开始减少进程时,它将惊慌失措。这就是恰当地命名为“内存不足”杀手的工作。这只是为了避免立即重新启动,并且,如果OOM-killer的启发式方法运行良好,则实际上可以确定正确的过程。被杀死的进程不会像警告一样多,它们只是被信号终止。再次没有涉及用户空间异常。
TL; DR:在过度使用的内核上捕获bad_alloc
异常几乎是无用的。
答案 1 :(得分:1)
Linux过量使用内存。这意味着malloc()并没有真正分配,只是让进程相信它已经分配了。实际分配发生在页面故障后真正需要内存页面时。因此,即使malloc()需要分配的内存大于实际可用内存,也不会失败。
我已经测试了您的代码,并且在我的Linux上也崩溃了。要真正拥有bad_alloc异常,只需禁用过量使用即可。
这在Virtual memory settings in Linux - The Problem with Overcommit中有详细描述。
为我禁用过度使用作品
sudo sh -c 'echo 2 > /proc/sys/vm/overcommit_memory'
此设置有两个缺点。当malloc()返回NULL时,即使在此设置之前它可以工作,也会有更多情况。第二个缺点是这不是持久性的,并且在重新启动后会恢复为原始值。为此,您需要更新启动脚本。
编辑:
要使其在重新启动后正常工作,您可以
须藤vi / etc / crontab
(请参见man 5 crontab
)
然后添加行
@reboot root echo 2 > /proc/sys/vm/overcommit_memory
答案 2 :(得分:0)
bad_alloc
失败,将抛出 malloc
,如果内核调用更多内存失败,则malloc
将失败。在Linux的情况下,内核通常会让进程成功占用更多内存,然后,如果Linux感觉进程正在占用大量内存,它将被杀死。这与Linux仅拒绝为进程分配更多内存(这会导致格式正确的bad_alloc
)不同。
解决方案是将new
/ delete
(这是简单的malloc
/ free
调用和对malloc == 0
的检查)覆盖,以实现以下功能还跟踪内存使用情况。然后,当内存使用率过高时,new
可能会抛出bad_alloc
,然后Linux毫不客气地将其杀死。