主要结论:
使用void * prt = reader->type().create_data();
创建的void *
指针,需要通过reader->type().delete_data(prt);
来释放,否则会出现内存泄漏问题
起初为了方便开发者编写业务代码,我写了几个基于 fastDDS 的封装类,大大提高了开发效率。
但是在压测时,发现长时间运行程序,程序的VmRSS 不断增长。判断可能是内存泄露。
于是开始调查,从业务代码到库代码,将返回指针换成智能指针,排查每个 new
等等,但始终没找到彻底解决的方法。
这时怀疑是不是 DDS 本身收发信息出了问题。便搭建了简易的测试项目,惊奇地发现没有出现泄露问题。经过细致对比,才发现之前写的封装类有问题。
因为之前要解决各种泛型问题,我使用了 void *
作为参数或者返回值。而在封装类内部需要创建一个消息实体指针,因为不知道消息对象类型,采用了用 reader.type().createData()
方法来创建消息对象。当初理解该方法创建的是智能指针,会自动释放,就没有太在意,也没特别考虑释放问题。
而后面使用中,又将其用 void *
的方式将其返回出了代码作用域,从而造成了问题。
修改方法很简单,假设消息对象指针是 msgprt
,在使用完成消息对象之后调用 reader.type().deleteData(msgprt)
即可。修正了相关代码,测试没有再发现泄露问题。
代码如下:
// MySubscriber 是对 fastDDS Subscriber 类的分装类
void MySubscriber::SubListener::on_data_available(DataReader *reader) {
void *st_ = reader->type().create_data(); // 通过 reader 创建一个内存区域 用于暂存消息
SampleInfo info_;
if (reader->take_next_sample(st_, &info_) == ReturnCode_t::RETCODE_OK) {
if (info_.valid_data) {
if (on_data_ != nullptr) { // on_data_ 是收到消息后的业务层回调函数
on_data_(st_); // 将数据传送出去
}
}
}
// 这里是重点,当没有获得消息或者业务层回调函数处理完后 释放掉于用暂存消息的内存
reader->type().delete_data(st_);
}
但这并非最好的解决方法,因为 如果消息对象作为返回值,出了代码作用域后,必须要求调用者自行执行释放代码。这样一是不方便,不优雅,二是会给项目带来开发和维护上的风险。
更好的解决方法是,采用泛型和智能指针技术,这样可以在内部创建消息对象的智能指针,并在返回时移交给调用者;调用者用智能指针接收返回值,当调用者的代码区域执行完成后,对象会自动释放,从而解决了上面的两个隐忧。
虽然写法上比用 void *
稍微啰嗦了些,但是很值得的。