我有以下代码来分配大量数据,如果它超过可用内存(这里是 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;
}
永远不会捕获异常。应用程序刚刚终止,或使操作系统崩溃。
我做错了什么?
最佳答案
您的new
运算符必须从某处获取内存。由于 new
是用户空间代码,与实际内存没有任何联系,它所能做的就是通过系统调用 sbrk()
或系统调用 mmap()
用于一些内存。内核将通过将一些额外的内存页面映射到进程的虚拟地址空间来做出响应。
碰巧,内核返回给用户进程的任何内存页面都必须清零。如果跳过此步骤,内核可能会将敏感数据从另一个应用程序或自身泄漏到用户空间进程。
还有一种情况是,内核总是有一个只包含零的内存页。因此它可以通过简单地将这个零页映射到新的地址范围来满足任何 mmap()
请求。它将这些映射标记为写入时复制,这样每当您的用户空间进程开始写入此类页面时,内核将立即创建零页面的拷贝。然后内核将四处寻找另一页内存来支持它的 promise 。
你看到问题了吗? 在您的进程实际写入内存之前,内核不需要任何物理内存。这称为内存过度使用。当您 fork 一个进程时,会发生这种情况的另一个版本。您认为当您调用 fork()
时内核会立即复制您的内存吗?当然不是。它只会对现有内存页面进行一些 COW 映射!
(这是一个重要的优化机制:许多启动的映射永远不需要额外的内存支持。这对于 fork()
尤其重要:此调用通常紧跟一个 exec()
调用,这将立即再次拆除 COW 映射。)
缺点是,内核永远不知道它实际需要多少物理内存,直到它无法支持自己的 promise 。这就是为什么您不能依赖 sbrk()
或 mmap()
在内存不足时返回错误:您不会耗尽内存,直到您 < em>写入映射内存。系统调用没有返回错误代码意味着您的 new
运算符不知道何时抛出。所以它不会抛出。
相反,当内核意识到内存不足时,它会崩溃,并开始关闭进程。这就是名副其实的 Out-Of-Memory killer 的工作。这只是为了避免立即重新启动,而且,如果 OOM-killer 的试探法运作良好,它实际上会触发正确的进程。被杀死的进程不会得到警告,它们只是被一个信号终止。再次不涉及用户空间异常。
TL;DR:在过度提交的内核上捕获 bad_alloc
异常几乎没有用。
关于未捕获 c++ bad_alloc 异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51674575/