内存池在底层也是调用了malloc函数,因此内存池也是必然会溢出的。而且内存池可能会比直接调用malloc更早的溢出,看看下面的代码:
#include <iostream>
#include <ctime>
#include <vector>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;
int _tmain(int argc, _TCHAR* argv[])
{
boost::pool<> pl(1024*1024);
clock_t clock_begin = clock();
int iLength = 0;
for (int i = 0; ;++i)
{
void* p = pl.malloc();
if (p == NULL)
{
break;
}
++iLength;
}
clock_t clock_end = clock();
cout<<"共申请了"<<iLength<<"M内存,程序运行了"<<clock_end-clock_begin<<" 个系统时钟"<<endl;
return 0;
}
运行的结果是“共申请了992M内存,程序运行了 1265 个系统时钟”,意思是在分配了992M内存后,内存池已经不能够申请到1M大小的内存块了。
5.2 内存池的基本原理
从上面的两个测试可以看出内存池要比malloc溢出早,我的机器内存是1.5G,malloc分配了1916M才溢出(显然分配了虚拟内存),而内存池只分配了992M就溢出了。第二点是内存池溢出快,只用了1265微秒就溢出了,而malloc用了69421微秒才溢出。
这些差别是内存池的处理机制造成的,内存池对于内存分配的算法如下,以pool内存池为例:
1. pool初始化时带有一个块大小的参数memSize,那么pool刚开始会申请一大块内存,例如其大小为32*memSize。当然它还会申请一些空间用以管理链表,为方便述说,这里忽略这些内存。
2. 用户不停的申请大小为memSize的内存,终于超过了内存池的大小,于是内存池启动重分配机制;
3. 重分配机制会再申请一块大小为原内存池大小两倍的内存(那么第一次会申请64*memSize),然后将这块内存加到内存池的管理链表末尾;
4. 用户继续申请内存,终于又一次超过了内存池的大小,于是又一次启动重分配机制,直至重分配时无法申请到新的内存块。
5. 由于每次都是两倍于原内存,因此当内存池大小达到了992M时,再一次申请就需要1984M,但是malloc最多只能申请到1916M,因此malloc失败,内存池溢出。
通过以上原理也可以理解为什么内存池溢出比malloc溢出要快得多,因为它是以2的指数级来扩大内存池,真正调用malloc的次数约等于log2(1916),而malloc是实实在在进行了1916次调用。所以内存池只用了1秒多就溢出了,而malloc用了69秒。
5.3 内存池溢出的解决方法
对于malloc造成的内存溢出,一般来说没有太多办法可想。基本上就是报一个异常或者错误,然后让用户关闭程序。当然有的程序会有内存自我管理功能,可以让用户选择关闭一切次要功能来维持主要功能的继续运行。
而对于内存池的溢出,还是可以想一些办法的,因为毕竟系统内存还有潜力可挖。
第一个方法是尽量延缓内存池的溢出,做法是在程序启动时就尽量申请最大的内存池,如果在程序运行很久后再申请,可能OS因为内存碎片增多而不能提供最大的内存池。其方法是在程序启动时就不停的申请内存直到内存池溢出,然后清空内存池并开始正常工作。由于内存池并不会自动减小,所以这样可以一直维持内存池保持最大状态。
第二个方法是在内存池溢出时使用第二个内存池,由于第二个内存池可以继续申请较小块的内存,所以程序可继续运行。代码如下:
#include <iostream>
#include <ctime>
#include <vector>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;
int _tmain(int argc, _TCHAR* argv[])
{
boost::pool<> pl(1024*1024);
clock_t clock_begin = clock();
int iLength = 0;
for (int i = 0; ;++i)
{
void* p = pl.malloc();
if (p == NULL)
{
break;
}
++iLength;
}
clock_t clock_end = clock();
cout<<"共申请了"<<iLength<<"M内存,程序运行了"<<clock_end-clock_begin<<" 个系统时钟"<<endl;
clock_begin = clock();
iLength = 0;
boost::pool<> pl2(1024*1024);
for (int i = 0; ;++i)
{
void* p = pl2.malloc();
if (p == NULL)
{
break;
}
++iLength;
}
clock_end = clock();
cout<<"又申请了"<<iLength<<"M内存,程序运行了"<<clock_end-clock_begin<<" 个系统时钟"<<endl;
return 0;
}
运行结果如下:
结果表明在第一个内存池溢出后,第二个内存池又提供了480M的内存。
5.4 内存池溢出的终极方案
如果无论如何都不能再申请到新的内存了,那么还是老老实实告诉用户重启程序吧。