Archive

Posts Tagged ‘memory leak’

强引用智能指针与弱引用智能指针

February 25th, 2015 No comments

c++代码中最麻烦的就是内存管理了, 还好有了RAII,这样至少可以解放一点我们的脑细胞,不需要时时刻刻注意释放内存或者避免多次释放内存。现在很多人都使用std::shared_ptr 当然了,如果你没有用到C++11,那么可以使用boost::shared_ptr. 不过我们当时使用的不是shared_ptr. 而是学习了ICE以后自己倒腾出来的一个intrusive_ptr,俺们管它叫做Pointer, 非常类似于boost::intrusive_ptr。不过我们当时还不知道boost有这个东西而已(也可能当时还没有这个玩意儿)。

侵入式智能指针和非侵入式智能指针的差别还是挺大的,我们用一幅图来说明一下。 
smart_pointer
这幅图的左边是侵入式智能指针,其引用计数放在了被指向的object的里面,intrusive_ptr指向这个object即可。而右边是非侵入式智能指针,引用计数被单独放在一块区域(其中还包括一个弱引用的计数)。non-intrusive_ptr(shared_ptr, weak_ptr)指向object和引用计数区域。这样一来,对于intrusive_ptr,我们还是可以直接使用raw pointer, 当然这样并不好,也可以再把一个raw pointer传递给一个intrusive_ptr, 其引用计数不会有被中断的情况。而non-intrusive_ptr就不能这样,raw pointer传递给他们以后引用计数只能重新开始计数。还有就是由于侵入式智能指针的引用计数和目标object实例在一块, 那么可以更加有效的利用CPU cache。 而非侵入式的智能指针因为有两个指针,就会稍弱一点。但是呢,侵入式智能指针相比非侵入式智能指针也有一个缺点,这就是我们要讲的弱引用智能指针。 
那么弱引用到底是什么?有什么用处呢? 我们知道智能指针的引入是为了减少内存泄漏的问题,顺带着把野指针,多次释放内存等问题一块解决了。但是有的时候只能指针也会有内存泄漏问题,循环引用–就是这种情况。当两个object互相持有对方的引用,那么他们的引用计数都不会减少到0,那么这两个object也都不会被释放。解决的办法也就是弱引用. 当两个object需要持有对方的时候,其中一方持有对方的弱引用,弱引用是不增加(强)引用计数的–即上图右中的reference count,但是会增加weak reference count. 举个例子,有A,B两个对象实例,其中A持有B的强引用refB, 但是B只持有A的弱引用refA. 当除B以外的object不再持有对A的引用的时候,由于弱引用不会增加引用计数,所以A的引用计数会降至0,此时A会被释放掉,同时B的引用计数也会减少。这样就可以有效的解决循环引用造成的内存泄漏问题。

现在我们来看看弱引用是如何实现的。如上图右边所示,非侵入式智能指针有两个指针。其中一个指向对象实例,我们命名为px; 另外一个指向一块专门用来存放引用计数的区域,我们为之命名为pn. pn所指向的区域( 就叫RefCountZone吧 )里面一共有两个变量,reference count和weak reference count, 他们分别记录强引用和弱引用的次数。当使用shared_ptr的时候,reference count就会加1,而weak reference count不会有变化。而使用weak_ptr的时候, weak reference count就会加1,但是reference count不会有变化。这样一来当所有的强引用都消失了, 那么reference count就会变成0,这个时候就可以删除px所指向的对象实例了。但是值得注意的是在这个时候pn所指向的区域不一定被删除。我们假设这个时候还有弱引用到这个区域,那么pn指向的区域是不会被删除的。这时候如果我们需要使用weak_ptr指向的object 实例的时候,首先需要调用weak_ptr::lock方法,该方法可以获得一个shared_ptr(但可能是空)。weak_ptr::lock首先检查pn->reference count是否为0,如果是0 的话,那么说明px指向的实例已经被删除,那么lock方法会失败,返回一个不指向任何实例的shared_ptr,如果pn->reference count不为0,那么说明px所指向的实例还存在,这个时候可以返回一个该实例的shared_ptr. 当所有的弱引用都消失的时候,也就是pn->weak reference count == 0的时候,同时pn->reference count也是0,那么说明已经没有任何对这个实例的引用了(不论是强引用还是弱引用),这是pn指向的区域就可以被安全删除了。 
从上面我们也可以看出弱引用很巧妙的利用了非侵入式智能指针的RefCountZone。同时利用了reference count和weak reference count来分别控制强引用和弱引用的个数。但是侵入式智能指针就不能很好的实现弱引用了,因为如果我们要实现弱引用,那么就需要一块区域来存放所引用的次数,而且这个块区域不能随着object instance的消亡而消亡,否者当强引用完全消失但还存在弱引用的时候由于object instance已经被删除导致弱引用计数无法被访问。如果我们也想像非侵入式智能指针 一样新开辟一块区域来存储ref count, 那么就变得和shared_ptr/weak_ptr一样,没有重新实现的必要了。所以如今在使用侵入式只能指针并且有循环引用风险的地方,我们会将其中一方持有对方的智能指针修改为原始指针或者是引用。

内存泄露 (续)

October 29th, 2011 No comments

  在现场的一个服务有巨大的内存泄露的问题,最近一直都在检查这个问题。虽然看起来还有一点泄露,不过已经能够让现场可以撑得更久一点了。

  现场的一个播流的服务(实际上时一个速率可控的http下载的服务器端),由于前一段时间性能不能达到客户的要求,而大幅修改过很多的代码。而且又需要立马上线,结果不能很好的测试就把匆忙修改的程序部署到了现场。现在想起来真实悲催啊,因为性能问题而修改累了一段时间,现在有需要给这个没有充分测试的程序做扫尾动作。

  当现场报说有大量内存泄露以后,我就开始着手使用分析工具来看看泄露到底是什么地方引起的。 我选择了google performance tool来查看内存泄露的点. 安装google perf tool测试一段时候,根据google perftool的提示和code review找到了一些内存泄露的代码。但是奇怪的就是修复了这些泄露点以后重新build一个版本做压力测试发现还是有内存泄露。这个让我觉得很是奇怪,再次使用google perftool测试,但是该工具没有报告任何的内存泄漏点,这个让我一度怀疑这个工具是有问题的。不过后来一想,我们的应用用到了很多的stl class,特别是std::map std::list等东西, 如果这些实例析构以后所占用的内存会被释放,而google perftool是要在程序退出以后才能报告是否有内存泄露。如果一个程序在运行的过程中不断的向一个list或者map插入新的记录但是忘了删除的话,这个依然是一个内存泄漏点。为了验证我的想法,我在google perftool的代码加入了一个signal的响应函数,当接收到这个siganl的时候就做一次内存泄露报告。然后使用这个修改后的google perftool进行测试,程序在大压力的环境下跑了半个小时以后,把所有的测试客户端都停掉(防止在report memory leak的时候还有代码需要做内存分配/释放操作)。然后查看内存泄露报告,发现确实如我所想,内存泄露大都来之std::map中的record没有删除而造成的。

  当我以为这些内存泄露点都修复好了程序就应该没有什么问题了,接下来的压力测试让我觉得有点摸不着头脑了。程序在大压力的情况下跑了48+小时后发现有400M+的内存泄露,停掉所有的客户端以后内存使用并不会回到最初状态。而在此使用google perftool进行检测却找不到有内存泄漏点。迷惑了很长时间以后google了一番找到了http://www.nosqlnotes.net/archives/105这篇文章,也就是GLIBC的内存分配/释放的缺省行为可能造成内存不会被正常交还给kernel(当然我的程序是否是这个问题还需要进一步确认)。

  查找完内存泄露点以后觉得很有必要做一个记录,也好给自己做一个提醒。

1.  malloc / new 以后没有free/delete。

2.  加入到map list vector等容器中的record没有及时删除

3.  智能指针的循环引用,例如A和B是两个实力,其中A有一个智能指针指向B,而B也有一个智能指针指向A。如果不主动调用A或者B的资源释放方法的话A和B均不会被析构

4.  还有就是没有考虑到所有的资源释放情形,导致在特殊情况下资源释放的方法不能被调用到

     例如,在调用了epoll_ctl(…, EPOLL_CTL_ADD , …)以后没有检查返回值,在某个情况下这个函数可能会返回错误,这个时候就应该调用资源释放函数(这个socket已经不可用了)

5. 还有啥?想到了再补充一下…

Categories: programming Tags: ,

内存泄露啦

October 19th, 2011 No comments

内存泄露真的是一个很令人纠结的问题,于是使用了诸如智能指针的东西来避免这个问题,但悲哀的是就连这样还是造成了内存泄露。仔细查看源代码以后发现了问题的所在。

程序使用的智能指针是基于引用次数的,也就是说当某一个object被引用的次数>0的时候,这个object就会保留在内存中。当引用次数减少到0的时候,也就是没有任何一个地方在使用这个object的时候这个object就会被删除。

在程序中有一个地方对于这个智能指针的使用不当导致了引用次数不能正常减少到0,引起了内存泄露。程序中有A和B两个对象,其中B是A的一个member。同时B有一个智能指针指向A(需要在运行的时候使用A的部分功能)。在一切正常的情形下面,当调用A的close的时候,A会调用B的close,同时B会将指向A的智能指针置空(也就是不指向A了),这个时候就是正常的不会产生内存泄露。但是异常的情况下,A的close调用B的close的时候,B不会清除对A的引用,这个时候由于A还有人引用–B还有一个指针指向A,所以A不会被析构,导致了内存的泄露。

这个问题提醒我即便使用了智能指针也要小心的使用,及时清除对对象的引用。还有就是最好不要交叉引用–A引用B,同时B引用A。

Categories: programming Tags: ,