最近被公司一个新产品的内存泄漏搞得焦头烂额,该产品属于主线代码的一个分支,代码大致相同,只是硬件很不一样,但是主线却没有内存泄漏,分支每天都会有400M的泄漏,临近过点,亚历山大。
内存泄漏这次在操作系统层面总结下来分为四类:堆泄漏,栈堆积,系统资源泄漏,内存碎片
从出现概率来说逐渐降低,所以定位顺序也依次展开
1.堆泄漏
也就是new/malloc没有对应释放,这个一般来说直接用valgrind直接跑就完事,这里看到安装valgrind网上大部分都是官网下载源码到服务器编译安装,由于该产品glibc版本较低且没有安装gcc,也找不到配套的低版本的gcc,于是直接下载了valgrind的rpm包,直接拷到服务器安装,一顿猛操作后发现,有打印一些明确泄漏的,但是最多几个字节,剩余的可能泄漏点都是程序初始化申请的固定内存,不太可能造成每天几百兆的泄漏。
防止工具不给力,还在代码里在几个频繁申请内存的地方加了引用计数,观察到内存增长时引用计数也没有增长,大概不是这个原因。
2.栈堆积
可能时某个队列积攒了大量数据导致的内存增长,但是增加打印观察队列数也没有增长
3.系统资源泄漏
有可能是线程句柄、套接字句柄、文件句柄没有正常关闭,比较分支和主线代码,发现有一处pthread_create后,线程结束后没有回收资源,需要调用下pthread_deatch, 同步后跑了一晚还是有泄漏,继续定位
4.内存碎片
在内存增长后观察日志时,突发奇想把业务开关关闭了,发现内存全部降了下来,降低到业务开始前的内存,这进一步证明没有发生堆泄漏,偶然看到一篇帖子,glibc有内存空洞(碎片)的问题,看了下确实和我这个情况有点类似,glibc针对小内存有一套有自己的缓存管理,并且还有可能造成内存碎片无法释放,大内存才会使用mmap,但系统调用消耗大一点
之前有个大小为20字节的结构体因为很频繁创建,为了避免频繁触发拷贝构造,每次都是new一下用完delete掉,认为效率会快一点,于是更改这一处new对象为直接从栈声明对象,再跑一天发现内存不再增长。
这里还是疑惑为什么主线版本也是同样的代码为什么没有漏,看了新产品和主线的glibc版本,新产品是glibc-2.17.292,主线是glibc-2.17.196, 新产品还高一点,难道高版本引入了某些缓存释放异常,精力有限,暂不深究。
后记:
惨绝人寰,跑了两天之后再次发生内存泄漏,太难了,无奈之下,只能自下而上注释模块代码直接返回成功,编译版本测试,又花了好几天时间,最终层层注释,发现到了注释了某个模块后内存不再增长,走读了下代码发现有个队列只在析构时释放了数据,正常情况下不停止任务不会析构,队列就会一直增长,停止任务会触发析构内存又会降,修改后再次跑应该是解决了,内存泄漏从出现概率上来看,分为常发型、偶发型、一次性、隐型,这次我这个总结来就是隐型的栈堆积导致。
还有之前打印队列数的时候没有考虑周全,泄漏的这个库没考虑到,没有打印它,失算失算。
至于为什么valgrind没有检测出来队列中的指针没有释放,大概是生成检测报告时我都是让程序正常退出的,退出的时候会触发析构,所以检测不出来,内存着实比cpu优化难搞。
来源:oschina
链接:https://my.oschina.net/u/4349518/blog/4287981