首页 > 代码库 > 内存泄漏是个什么狗东西
内存泄漏是个什么狗东西
内存泄漏是个什么狗东西(hdd)
为啥写这个东西?
最近继续三四天一直在跟踪大家反映很久很久很久的ONVIF协议服务器被NVR或ODM(ONVIF Device Manager)工具探测就会出内存泄漏的问题。后果老严重了,比如三板IPC,内存比较大,有时候24小时就崩了,有时候72小时会崩,而双板IPC,内存比较小,很短的时间就会发现有内存泄漏,用着用着,IPC可能就会出现OOD(Out of memory)内存不足,或者其它异常。自然我们要去跟踪ONVIF协议的问题,然后确实发现ONVIF方面有相应的释放措施并没有做,做上应该没有了吧。是的,有所改善,但仍然有。而且相当的隐蔽,需要连接探测一百次可能才发现有泄漏。就这样,我们虽然知道有问题,但不知道问题在哪里,各种原因纠结,这个问题至少被放了半年之久。期间被客户投诉过不知道多少回!
写这篇文章的目的在于把这一次经历分享给大家,让大家对软件开发中的问题方法有些更深一点体会,多了解几种检测问题的方法工具,以及如何避免出现这种问题的方法。如果你够SHARE精神,不妨也写些文章分享给大家,让大家也受益那就“善”莫大焉了。
如果能在这方面对大家都有提高,那么对各人自己,对公司都是非常益处的。实际上,我们每天所花的时间都是白花花的银子啊,如果我们的这种问题少一些,是不是节省了成本,提高了效益,大家自然都会受益,所以,我倡议大家积极响应,为了自己和大家,把自己的能力提起来吧。
什么是内存泄漏?
通过我们说内存泄漏(memory leak,可以使用memory leak作为关键字搜索相关的文章,工具,论文,资料,或许能找到不少国外先进的思想,技术,工具),主要是指在我们写程序的时候,在堆(不要告诉你还不知道什么是堆(heap),如果是这样的话自觉搬小板凳学习操作系统原理及最基本C语言)上分配了内存,但由于我们不小心(我们应该都是好人吧,没人会故意埋个泄漏吧),比如函数内有malloc(C++常用new)的操作,但在return时并未free这一块内存。有时候可能是正常情况都会free但出现某种异常的时候就忘记了free(C++里常用delete),这种函数如果调用的频繁,那系统离死就不远了。
其实,但还其它资源的泄漏,比如文件句柄,SOCKET句柄,或者其它任何资源,都得有借有还,再借不难。内存泄漏的概念,方法,意识都可以扩展到其它资源管理(内存当然也是一种资源,明白?)中去。
内存泄漏的危害?
内存泄漏的危害可大可小,频繁使用时即使只有很小(哪怕几个字节)的泄漏,对于整个系统来讲也是不可承受之重。可小是什么呢?是因为使用的频次,范围较小,即使有泄漏,但影响不会太大,但这是一颗定时炸弹,因为我们不知道什么时候就有哪个家伙使用这个要命的泄漏函数或者类,随时都会炸毁我们的系统。
如果系统的规模比较小,即使有问题也还好,一行行代码看下来也有可能把问题给解决了。但目前我们面对的系统都是数十万行(我们的NVR至少是在四十万行以上)的规模,要找出某个要运行几十个小时甚至一个星期才能出现的问题是多么的不易。可能找过这类问题的你可能更有切身体会,没有头绪的时候想死的心都有,特别是在而对客户,领导的压力下,对于又拥有强大责任心的你,是不是“亚力山大”?
说一个事实大家可能更有体会。一只IPC被装在某高档别墅区,小区里的富人多,开的车自然好。有个业主的宝马X6(百把万人民币啊)停在小区的停车场,结果车子被盗了。业主自然会找物业啊,物业说不怕,我们有监控,能够给警察提供线索。不幸的是,某一时刻IPC重启了一次,前前后后都没有车子的录像数据,只能认为是IPC的重启期间人家盗走了汽车。业主找物业,物业找做IPC的厂家,索赔厂家赔人家的宝马啊。嗯,那个程序猿,你来说说系统为什么重启了,是不是你搞出来的内存泄漏?你是不是应该赔人家一个汽车轮子啊。其实前些年的723动车事故都有可能是某个内存泄漏引起的。
凭心而论,作为一个职业首先的程序员,建立一个下稳固的系统应该永恒的基本理想。朋友,你觉得呢?
内存泄漏的危害真的要多大有多大,足够把你自己毁掉(比如那个写出内存泄漏的家伙不幸也坐上那趟高铁)。
内存泄漏是怎么产生的?
在前面也有简单提到,简单一点就是因为“有借不还”,或者叫“有借忘记还”,总之是没有还。再加上咱们的操作系统又是个老好人,只要他有,他总会给你,而且无怨无悔直到他超过他的底线(超过为每个进程分配的内存空间)时会直接KILL掉这个“老赖”,不然我们的老好人自己就会“屎”掉了。
即便是一个函数内部,代码行数多了,出口多了,这种事故还是很常见的,如果有这个意识可能出错的几率会小一些。特别是当我们接手一个老项目,对这个项目也不甚熟悉,这个函数几百上千真是难得看,很容易就在里面加一个出口,却不幸的忘记了释放前面已经分配的资源,娃哈哈,你无声无息中就为系统装上了一个定时炸弹,定时时间取决于泄漏的大小与被调用的频率。
现实的程序世界中比如象我们所研发设备中,多线程操作也太常见了,比如在一个线程A里分配(生产者)资源,而在另一个线程B里使用(消费者),那么释放就存在到底是A还是B在释放内存?什么时候释放?这都取决你实现的功能即业务本身的需要。保不齐,没有处理好就埋下祸根儿了。
怎么跟踪内存泄漏?
我们现在都知道了内存泄漏是坏人,是犯罪分子,而我们就是专门抓捕坏人的捕快。捕快可不是那么好当的,得有几把金刚钻儿才行。那么朋友,你想想有什么很有效的方法来抓捕这些坏人吗?
在这里,我只单说一下怎样利用valgrind,观察进程status,maps信息来检测发现内存泄漏。
解决任何程序中的问题都得三分分析七分定位(你也可以二八开或者一九开都行)。那些鬼问题一般不可能只靠分析现象或者分析代码就能很快找出问题了,如果你有这本事,能收我为徒弟吗?
首先,我们就要能发现问题,比如系统应该运行一天跟运行两天,内存的变化应该是极小的,不会有明显的变化。而突然有一天,我们发现,第二天内存涨了一截,又没有什么特别的操作,那么我们就应该想想是不是哪个地方有泄漏了。接下来我们应该看看当时启动了哪些功能,是否去掉某些功能就能消除内存异常增长现象。给你一个小技巧,我们可以采用二分法,逐步启动或去掉一些功能,最终可以定位到某一个软件功能有问题,能定位到软件功能,问题也就算解决一成了。哦,对了,可以使用“cat /proc/xxx/status”命令观察vmData的值,试验前后两次比较就可以内存的使用情况,需要注意的是vmData的计量单位是kB,也就是某个地方有几十上百个字节,一个试验可能看不出增长,但不要因此就认为不存在泄漏了哦。这一步,大多数对系统熟悉的开发工程师和有兴趣的测试工程师都可以进行,真不是什么难事儿!但却非常重要,相当于找到了问题的突破口,找不到突破口就想想馋猫猫怎么吃缩头乌龟是什么感觉吧!
找到突破口,接下来就应该程序员上场了,实际在这之前应该还是一个角色露个脸,那就是对这个系统最熟悉的人,我们叫他系统架构师吧。他应该从系统架构断一下可能是什么模块有问题了,提找负责开发或维护这个模块的工程师入场,虽然问题不一定是出在这个模块,但总得有一个入口。如果这个排头兵是你,也不要苦恼,不要自认为这个问题肯定不是我的问题而带有情绪,哪怕之前都有这样的先例,说不定这次真是你的问题呢。
到了源代码这一层面之后,快速到定位泄漏就很重要,这取决是否能否快速的解决问题。定位问题点之后,这个点可能是另一个模块的实现,那么就马上把问题传递到另外的模块负责人,依然类推,必然最终找到那只可恶的坏人。这里说一简单直接有效寻找泄漏点的方法即经过简单分析之后开始屏蔽部分代码,直接屏蔽最少的代码不出现泄漏为止。
你一定要意识到,有些泄漏很大,也有一些泄漏很少,几个字节如果调用的频繁,时间长了也会一个黑洞。所以我们一定不要认为用你的爪子点击几下或几十下没有出现泄漏就万事大吉了,只不过是调用的次数少了一点而已。那我们要点击10000次操作怎么搞?如果驱动模块是UI界面,那就写几行代码驱动一下你认为可疑的地方,就不必真的手动去点击了,对不对?如果你的驱动模块是WINDOWS程序,那恭喜你,你能很开心的使用神器“按键精灵”来帮你点,甚至可以录制操作轨迹。我这次测试就是使用这个工具帮我点击一百次甚至一万次的,为俺节省了不少体力。
俺说的这个思路虽然不是什么高大上的东西,但确实简单,直接,有效。就是可能有些费体力,屏蔽代码再做测试,如此反复确实是个体力活儿。如果你有什么高精尖的方法,可以取点巧的办法(普适性方法),透露一下让俺也学习一下,可以么?
有什么预防措施来减少这种问题吗?
喂,老兄,说了这么多,干了这么多年,你就没想过或者没见过有什么办法来预防这些问题吗?是的前期预防比后期解决容易的多,或者是要容易的多吧,呵呵,也未必。这可能是个复杂的问题。我先介绍一下几种预防措施,再来讨论一下为什么这是个复杂的问题。
预防措施一,培养自己有借有还的意识,在分配内存的第一时刻起就应该时刻将其铭记在心,走的时候(return或者break)一定要还,有多少次分配,那么在当你不再使用这些内存的时候一定还给系统。OK,只要你愿意做,认真的去做,应该能够做到吧。
预防措施二,自动分配自动释放器。这个方法可能只在C++(或者C里也可以实现, 你有兴趣的话可以自己研究一下)中有用类的构造函数和析构函数来作对应的分配与使用工具。嗯,我随写几句代码作个演示:
class auto_malloc
{
public:
// 在构造函数里分配内存,当声明这个类的实例时就会分配
auto_malloc()
{
m_pBuffer = new char[1024];
}
// 最为神奇的在这个地方,当变量的生命周期结束的时候,自动就给把内存释放了,我们爱在什么地方return就什么地方return,再也不用担心忘记释放内存了,朋友,你觉得怎么样?
~auto_malloc()
{
if (m_pBuffer)
{
delete [] m_pBuffer;
m_pBuffer = NULL;
}
}
// 可以用类似的方法来使用你分配的内存
char* using_buffer()
{
return m_pBuffer;
}
private:
char* m_pBuffer;
};
// 一般用法也就类似这样
int test_auto_malloc()
{
auto_malloc am;
char* buffer = am.using_buffer();
if(buffer)
{
// 做你想对buffer做的任何事
}
// 为啥我们没有释放am分配的内在,因为am的析构函数会自己释放撒,以后有人随便加return也不会影响这块内存的释放
return 0;
}
值得一提的是,这点小技巧虽然也不是什么高深的东东,但真的很有效。但用这个东西也要搞清楚他的实现原理才能用在适当的地方。如果在上面的例子中,如果想test_auto_malloc函数之外用也不大适用了,这是要知道的。因为am的生命周期就在这个函数里,出了这个函数,分配的那点内存会与am同归于尽。
预防措施三:尽可能多做一点单元测试。这里分两种,自己写的,这样的可能出现问题可能性小,因为对自己写的东西最熟悉。第二种,使用第三方的库,因为第三方的库或模块有没有问题不好说,所以不论哪种都应该尽可能作多一点的测试。微软公司的程序员平均每天写100行代码,其实不是人家写的不够快,而是他们做的测试多,BUG少,质量好。了解一些单元测试的方法对代码质量的提高非常有好处,比如写些测试代码都能遍历到所有路径,让所有路径所跑一遍;测试一下参数的合集(正常的,异常的)是否都能正常工作;长时间调用或者频繁调用会不会有泄漏或者性能问题。是不是线程安全(多线程同时访问必须保证数据的正确和功能正常)的?
预防措施四:代码检查。代码检查一般在程序员之间进行,双方都熟悉代码才有可能性。不仅能检查出可能存在的内存泄漏,其它问题也可能查出来。
预防措施五:由系统架构师或者主程序员(其实你有心,可以自己做一个自己负责模块的资源管理表)作好资源管理工作。比如哪个模块有动态内存的情况,分配的大小,使用的频次。不仅包括堆上的内存,全局内存,文件句柄,SOCKET句柄等其它重要资源都做一下登记工作。当涉及这类资源的时候,在代码审核时就作重点关注,这样对降低内存泄漏这一类问题是有好处的。
差不多这就是我能想到的预防措施,你有更好的法子吗,share给我呢,不要这么小气嘛!显然预防措施都是需要成本的,也就是执行每个措施都是需要花钱的,那么这个成本是花在之前还是花在之后?是之前多花一些还是之后多花一些,比例问题?我的建议是将更多的精力花在预防上,主发是因为新增的东西涉及的范围小,容易查到问题,系统新增功能之前没有问题,之后就有问题,有很大的可能就是新增部分出问题了。而且事后面对的是整个系统,重现和定位都会很困难,而且需要对整个系统都熟悉的人员去分析,花费更高。但也存在问题,这种不直接产生收益的工作正常人的思维都不会愿意去做,特别是面对工期紧张的压力时更是如此。
你愿意也分享一些你的经验吗?
对于这篇文章,你否感到惊喜(哈哈,不要脸啦)?原来内存泄漏是这样的啊!对于我们开发人员中在论老牛还是小牛,都很重要,随便一个疏漏,足以置我们的系统于绝境。我希望大家都能在我们这样一个集体里能更多的学习新东西,掌握好更多一些技能,更多一些分享精神,最终目的都是让产品更稳健,更具竞争力,公司有更好的收益,从而我们自己也能同时受益。我们作为程序员,我们能够做的就是这些,让我们的程序更稳,性能更高,功能更强。
一个人的所见所得都是有限的,希望大家一起参与进来,是否能够一周,一周做不到,一个月写点什么经验分享给大家吧。比如,死锁问题怎么解决?CLOSE_WAIT怎么解决?比如其它什么都可以。
内存泄漏是个什么狗东西