上次有个项目中要用到rapidjson生成json格式的数据文档,由于第一次用rapidjson,中间出现了疑似内存泄漏的问题,具体现象就是程序占用的内存不断在增加,debug过程中发现只要用rapidjson生成json数据后,内存占用就会增长,于是问题定位到rapidjson。由于是开源库,所以不觉得是库内部导致的内存泄漏,那么极有可能就是用法不当导致的,经过一番试验,最后找到了具体的原因。
先贴上测试的代码,下面再进行分析:
#include <iostream>
#include <string>
#include <unistd.h>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
using namespace std;
using namespace rapidjson;
Document *doc;
int func(Document * document)
{
string cont_str1 = to_string(time(NULL));
string cont_str2 = to_string(time(NULL)+100);
/* 获取rapidjson内存分配器,这里因为需求原因,是通过全局变量doc获取的分配器,
* 这是最关键的地方,只要doc不释放,这个分配器分配出来的内存也就不会释放。
*/
Document d(kObjectType);
Document::AllocatorType& allocator = document->GetAllocator();
/* 插入数据 */
Value val(kStringType);
val.SetString(cont_str1.c_str(), allocator);
d.AddMember("string1", val, allocator);
val.SetString(cont_str2.c_str(), allocator);
d.AddMember("string2", val, allocator);
document->PushBack(d, allocator);
return 0;
}
int doc_print(Document * document)
{
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
document->Accept(writer);
cout << buffer.GetString() << endl;
}
int main(void)
{
doc = new Document;
doc->SetArray();
/* 往doc中连续插入数据,持续时间为20s */
for (int i = 0; i < 4000; ++i)
{
func(doc);
usleep(50000);
}
/* 将json数据打印出来。同时需要注意的是,退出doc_print()函数后buffer才会释放 */
doc_print(doc);
/* 用完之后要释放doc,如果不释放,之前在fun()中使用分配器分配的内存将不会自动回收 */
delete doc;
doc = NULL;
sleep(20);
return 0;
}
代码大概的流程就是先定义一个Document根节点,然后不断插入数据,持续20秒,之后将数据打印出来,然后释放根节点,最后sleep20秒,退出程序。这里的时间设置是为了能观察内存的使用情况。
根节点是一个全局的指针变量,之所以是全局的,是因为项目的需求是会不定时生成json数据,但是每隔一段(固定的)时间才会将这些数据统一写入json文件。如果是生成json数据的同时就写入文件,那么Document根节点用局部变量就可以了,就不会有内存泄漏的问题。
内存不断增长的原因和rapidjson的分配器有关,因为这里分配器是获取根节点的,只要根节点不释放,那么用分配器分配出来的内存是不会自动释放的。所以该程序的内存消耗情况应该是这样的:前20秒一直在使用分配器分配内存,所以程序占用内存应该会一直增加;在打印完数据后就释放了根节点,那么之前分配器分配的内存也就随之释放掉了,此时占用内存应该会将下来。运行一下程序,同时看下程序的占用内存情况,验证下我们的猜想:
结果:占用内存确实在不断上升,从1068上升到2124,在释放根节点后,内存就降下来了。所以使用完分配器后,要记得释放相应的Document。
另外,打印时调用的doc_print()函数中定义了一个StringBuffer类型的局部变量buffer,buffer也是要等到退出该函数之后(即生命周期结束后),才会自动释放。