您好,欢迎来到12图资源库!分享精神,快乐你我!我们只是素材的搬运工!!
  • 首 页
  • 当前位置:首页 > 开发 > WEB开发 >
    一次C++伪“内存走漏”的排查之旅
    时间:2020-11-02 12:09 来源:网络整理 作者:网络 浏览:收藏 挑错 推荐 打印

    前段时间做一个需求,需求用到一个本地词典文件。该词典原始文件超过2G,在效劳启动的时分加载到内存中,并且保持词典数据的热加载,也就是不停服更新词典数据到效劳进程的内存中。

    一次C++伪“内存走漏”的排查之旅

    之前有同事在其他项目中有热更新词典的代码,我就直接睦龃用了。这是典型的双Buffer词典。也就是顺序运转时期,内存中会同时维持两份词典:一份前台词典供运转时各处置逻辑检索,另一份是后台词典,在检测到目的文件修正时(经过反省文件mtime判别的能否更新)。在词典数据更新时,重新解析加载,最新的数据贮存到后台词典中。最后两个词典做0 - 1 切换,也就是前台词典变后台词典,后台词典变前台词典。

    词典类在效劳中采用的中心数据结构是unordered_map。前后台词典也就是会存在两个unordered_map。key是某某ID,value是词典原始文件逐行解析后重组出来的protobuf Message对象。

    在线下环境(非线上消费环境)测试的时分,自测完代码逻辑无成绩。喵了一眼机器基础目的,发现内存会屡次下跌。

    一次C++伪“内存走漏”的排查之旅

    本人画的:横轴是时间,纵轴是机器占用内存

    内存占用在 5-10G之间那次是第一次启动完成的时间,前面又延续涨了两次。疑心是有内存泄露,在把流量停掉以后,重启效劳。观测到内存依旧会规律下跌,且一个小时会涨一次。如此规律,让人不得不疑心是词典更新招致。词典文件是ceph挂载的,会自动更新,所以我简直没关注过。确认了一下词典的更新时间和更新频率。确实也是一小时更新一次,且其每次更新的时间和内存每次下跌时间相match。

    想尽快验证一下能否真的是词典更新招致的内存下跌,等着词典一次一次例行更新就太慢了。不过由于这个词典API判别词典能否更新是检测的文件修正时间(mtime),所以经过touch该词典文件,可以提早触发词典的加载。

    按理说双buffer的词典,在正常启动后暴涨一次内存是合理的。由于启动的时分外存中加载了词典的一个版本。一个小时之后词典更新,第二个版本的词典数据也会参加到内存。而彼时原先的前台词典虽然变成了后台词典,但是内存并不会立刻delete(持有旧词典数据的unordered_map)。由于能够运转的央求处置逻辑依然会用到旧词典。

    重新阅读这个词典API的完成。当内存中存在两个版本的词典后,等到词典第二次更新到时分(也就是第三个版本词典出现的时分),该完成逻辑是先创立一个词典对象存储第三个版本词典的数据。若其加载解析成功则原先的后台词典对象就会被delete(第一个版本的词典占用的内存被释放)。然后后台词典的指针指向刚新建的对象(第三个版本的词典正式成为后台词典),最后做前后台词典的切换(第三个版本词典成为前台词典,第二个版本的词典变成后台词典)。

    也就是说按照这个词典API的完成逻辑,内存中确实存在某个时辰存储着三份词典的数据,涨两次内存也说得通,但是当新的词典加载完成,上上个版本的词典对象是会被delete的。所以内存应该回落才对!难道是delete没有被触发吗?

    尝试了touch了几次词典文件发现,确虚词典文件更新会招致内存延续下跌。但诡异的是后来我尝试缩减词典到一个特别小的大小,却察看到机器内存并不会下降!哦?这是词典API本身存在内存泄露的风险吗?和刚才看代码时的疑惑一样,上上版本的词典没有触发delete?但是经过屡次测试又发现这样一个理想:

    词典内存不会永远下跌,启动完成之后,最多涨两次,第三次也会涨但比较少,第四次五次更新词典文件,则简直不会招致内存的变化!假设说存在词典对象没有被正常delete,那么内存占用应该会继续下跌,而不是趋于波动。

    头疼。一方面内存不会有限下跌,不像是内存泄露;但另一方面词典增加却不会招致内存占用增加。

    这……让我在十月的深夜混乱了。成绩又兜回来了吗?这究竟是不是内存泄露?或许究竟是不是词典更新招致的呢?

    尝试了用一些工具来辅佐定位能否有内存泄露的风险,但一无所获。后来注释掉了每行词典数据重组成pb对象之后insert进unordered_map的代码,经测试词典更新确实不会再招致内存下跌。说白了实锤了内存下跌就是这两个前后台的unordered_map惹起的。但是经过加日志也能证明每次旧map对象的delete每次都有被调用到,也就是不存在第三个map对象没被delete的状况,那么为什么delete掉对象后,其占用的内存无法释放呢?

    遽然堕入绝境,坐困愁城。

    突然我灵光一现:会不会是glibc招致的持呢?我们都知道内存分配器,比如glibc的ptmalloc,有时分外存分配器的内存管理策略并不一定如我们所愿。

    经证明确实glibc有这样的内存分配策略:为了避免大对象频繁的内存分配和释放,glibc并不一定会把delete的对象内存立刻出借给操作系统,有时分能够继续让进程持有该内存。当后续再有大对象需求分配的时分,可以直接运用,而不再需求再去向操作系统央求内存。glibc这个策略其实是为了提高内存分配效率的,并且也不会有限占用内存,而是在到达某个平衡点之后内存便不再增长,这也和我所察看到的现象分歧。

    说究竟这其实不算是一次『内存泄露』。但是这个现象既然不会继续占用内存,那么究竟需不需求处置呢?在我的场景下,答案是一定的。由于我们的词典比较大,且不可控,当线上正常效劳的时分,内存也会正常下跌,其实是存在OOM风险的。在运转效率和效劳波动性之间相比较,自然要退让于波动性。

    那么怎样处置呢?虽然没有直接搜索到答案,但是直觉通知我一个更好的内存分配器或容许以处置。死马当活马医,于是我尝试了让顺序链接tcmalloc或jemalloc。最终jemalloc表现良好,可以渐渐释放掉多余占用的内存。

    一次C++伪“内存走漏”的排查之旅

    那些凹陷的线是加载和解析词表的进程中,突然飙下去的内存,但随机又很快回落,接着渐渐继续回落。其实jemalloc在针对大对象存储时,其功用表现也并不差,甚至运用了jemalloc之后效劳一次央求照应的耗时还有不少缩减。

    【编辑引荐】

    终于来了!!Pyston v2.0 发布,处置 Python 慢速的救星

    重学JavaScript第1集|变量提升

    Java顺序占用 CPU 过高怎样排查

    卡巴斯基:2020 Q3 DDoS攻击趋向报告

    在剖析了电商网站的开展演化后,我发现微效劳必需要上

    (责任编辑:admin)