头文件 pool.h

先定义引用计数基类 Ref 与自动释放池 autoreleasePoolRef 持有一个 _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 thisRef::autorelease 把自身塞入全局自动释放池,留待后续 clear 一次性回收。

run 函数用于压力测试:循环两轮,每轮创建 timesTTF 对象并全部放入自动释放池,随后 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 ~~~