转载自
最近工作上一个模块内存泄露,内存缓慢增涨,存在oom的风险,很多人搞了一个月,最后定位是protobuf格式的数据的增涨。
首选说一下,我定位内存泄露采取的经验吧。我一般利用gperftools,在进程起来之后,在一个时刻,先dump一个内存占用的heap,然后压力一段时间,等内存有明显增涨后,在dump一个内存占用的heap,这样比较两个heap的占用,图形化成svg,这样就能看到那个函数,哪个数据结构占增涨的内存最多了。然后就可以去看代码了,看看是否是内存泄露了。
然后咱们说一下,为啥会定位到protobuf格式的数据的增涨。主要是protobuf的数组格式的数据(repeated类型)。对于这类数据,protobuf的内存管理,是采用跟string、vectror等数据结构类似的动态增涨的内存分配策略。简单来讲,比如开始这个数组为1,发现存不下了就会翻倍为2,再之后就4,依次类推(或者也有1.5倍的翻倍)。对这样的数据结构,clear并不会回收内存。那这样问题就来了,时间运行长之后,我们的数据结构就保留成最大的内存占用。其实如果只是单线程,且用到的这样的数据结构并不多的话,这样是没什么问题。但是如果这样的数据结构很多,且在多线程的环境中,着就会变得很糟,会产生隐身内存泄露的问题。
假设我们有1000个线程,没个线程有10000个这样的动态增长的数据结构。如果这个动态增长的数据结构变大1K,这样增加的内存为1K*1000*10000,约10g,这就很明显了。
那这样的问题咋解决呢?那就是我们使用string、vector、protobuf的时候,考虑每次使用完后就主动释放内存,每次用的时候重新进行创建,而不是一直使用clear函数。这样应该就能解决问题了。这样虽然会损失一定性能,但如果我们用上tcmalloc,损失的性能基本上可以被tcmalloc消化掉。