头文件 pool.h
先定义引用计数基类 Ref 与自动释放池 autoreleasePool。Ref 持有一个 _RefCount 计数器,支持 retain / release / autorelease 三种操作。autoreleasePool 用单例模式管理所有延迟释放的对象,并在 clear 时统一 release。
这里特别要注意:~Ref() 必须声明为 virtual,否则通过基类指针删除子类对象时,子类部分无法析构,会造成内存泄露。
pool.h
#include <iostream>
#include"stack"
#include "vector"
using namespace std;
class autoreleasePool;
class Ref
{
protected:
unsigned int _RefCount;
public:
Ref() :_RefCount(1){}
virtual ~Ref()/*必须virtual 不然子类部分不能析构 导致内存泄露*/
{
cout<<"~ref"<<endl;
}
void retain();
void release();
void autorelease();
};
class autoreleasePool
{
public:
static autoreleasePool*_autoreleasePool;
autoreleasePool(){}
~autoreleasePool(){ data.clear(); }
static autoreleasePool*getInstance()
{
if(_autoreleasePool==nullptr)
_autoreleasePool = new autoreleasePool;
return _autoreleasePool;
}
static void destoryInstance()
{
if (_autoreleasePool != nullptr)
{
delete _autoreleasePool;
_autoreleasePool=nullptr;
}
}
vector<Ref*> data;
void clear()
{
for (int i = 0;i<data.size();i++)
{
data[i]->release();
}
data.clear();
data.reserve(10);
}
void addObject(Ref*add)
{
data.push_back(add);
}
};
autoreleasePool* autoreleasePool::_autoreleasePool = nullptr;
class TTF :public Ref
{
public:
long long testData[1000];
string name;
TTF(string name)
{
this->name = name;
cout << name.c_str()<<" create"<<endl;
}
~TTF()
{
cout << name.c_str() << " release"<<endl;
}
public:
static TTF*create(string name)
{
return new TTF(name);
}
};
实现文件 pool.cpp
Ref::retain / Ref::release 分别负责计数器加一和减一,减到 0 时 delete this。Ref::autorelease 把自身塞入全局自动释放池,留待后续 clear 一次性回收。
run 函数用于压力测试:循环两轮,每轮创建 times 个 TTF 对象并全部放入自动释放池,随后 Sleep(100) 观察内存占用,再调用 clear 统一释放。
pool.cpp
#include "pool.h"
#include "windows.h"
#include "stdlib.h"
void Ref::retain()
{
++_RefCount;
}
void Ref::release()
{
--_RefCount;
if (_RefCount == 0)
{
delete this;
}
}
void Ref::autorelease()
{
autoreleasePool::getInstance()->addObject(this);
}
void run(int times)
{
char str[100];
int i = 2;
while (i)
{
--i;
for (int x = 0; x < times; x++)
{
sprintf(str, "count is %d", x);
auto tmp = TTF::create(str);
tmp->autorelease();
}
cout << "after 100ms all TTF's objects will be released " << endl;
Sleep(100);
autoreleasePool::getInstance()->clear();
Sleep(100);
}
}
入口 main
int main(int argc, char *argv[])
{
run(5000);
system("pause");
return 0;
}
测试中的内存现象
用上面的代码跑 run(5000),观察任务管理器里的内存占用:程序刚启动时只有 0.4m,运行中峰值冲到 78.6m,跑完所有循环后依然占用 3.6m。
问题来了:为什么结束后还会占用 3.6m?用内存泄露检测工具跑了一遍,并没有检测到泄露。更神奇的是,用系统自带的内存清理工具清理一下,占用立刻回落到正常水平。
作为对比,再做一次测试二:不走自动释放池(也就是不延后释放),对象一创建出来就马上 release,这种方式下内存占用表现"正常",不会出现前面那种 3.6m 残留。
猜测与讨论
结合两次测试结果,基本可以猜测:当进程短时间内大量 new 内存、再批量 delete 时,操作系统并不会马上把这些内存完全收回,而是会预留一部分给当前进程,用来加速下一次 new 的执行速度——毕竟内存操作相对比较费时。
和操作系统老师讨论过这个现象,他也倾向于这个解释。只是 Windows 本身并不开源,具体实现只能停留在猜测层面。
over ~~~