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

之前有同事在其他项目中有热更新词典的代码,我就直接拿来用了。这是典型的双Buffer词典。也就是程序运行期间,内存中会同时维持两份词典:一份前台词典供运行时各处理逻辑检索,另一份是后台词典,在检测到目标文件修改时(通过检查文件mtime判断的是否更新)。在词典数据更新时,重新解析加载,最新的数据储存到后台词典中。最后两个词典做0 - 1 切换,也就是前台词典变后台词典,后台词典变前台词典。

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

在线下环境(非线上生产环境)测试的时候,自测完代码逻辑无问题。喵了一眼机器基础指标,发现内存会多次上涨。

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

内存占用在 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表现良好,可以慢慢释放掉多余占用的内存。

那些凸起的线是加载和解析词表的过程中,突然飙上来的内存,但随机又很快回落,接着慢慢继续回落。其实jemalloc在针对大对象存储时,其性能表现也并不差,甚至使用了jemalloc之后服务一次请求响应的耗时还有不少缩减。

【编辑推荐】

【责任编辑:

未丽燕

TEL:(010)68476606】

点赞 0

1709 ltsb 内存占用_一次C++伪“内存泄漏”的排查之旅相关推荐

  1. python减小内存占用_如何将Python内存占用缩小20倍?

    当程序执行过程中RAM中有大量对象处于活动状态时,可能会出现内存问题,特别是在对可用内存总量有限制的情况下. 下面概述了一些减小对象大小的方法,这些方法可以显著减少纯Python程序所需的RAM数量. ...

  2. 1709 ltsb 内存占用_腾讯游戏学院专家分析:Unity在移动设备的GPU内存机制

    导语 CPU和GPU是共享一份内存的吗?腾讯游戏学院专家Donald将在本文尝试以一张贴图纹理的虚拟内存占用为例,解答一些内存方面的问题.本篇主要分析iOS系统,后续会更新安卓篇. 开发手机游戏时,常 ...

  3. 1709 ltsb 内存占用_我的手机内存明明是8G,为什么用的时候只有4G?求解。

    展开全部 原因以bai下 1.内存8G是指整个ROM存储器空间du大小,而不zhi是可用dao空间大小.很多智能内手机,系统文件都会占用容部分存储空间, 2.为了给顾S4内置了安卓操作系统和很多创新功 ...

  4. 1709 ltsb 内存占用_「正点原子STM32Mini板资料连载」第三十二章 内存管理实验

    1)实验平台:正点原子STM32mini开发板 2)摘自<正点原子STM32 不完全手册(HAL 库版)>关注官方微信号公众号,获取更多资料:正点原子 第三十二章 内存管理实验 上一章,我 ...

  5. 写一段代码提高内存占用_记录一次生产环境中Redis内存增长异常排查全流程!...

    点击上方 IT牧场 ,选择 置顶或者星标 技术干货每日送达 最近 DBA 反馈线上的一个 Redis 资源已经超过了预先设计时的容量,并且已经进行了两次扩容,内存增长还在持续中,希望业务方排查一下容量 ...

  6. linux 内存占用_分享Linux内存占用几个案例

    案例一 问题 最近一台 CentOS 服务器,发现内存无端损失了许多,free 和 ps 统计的结果相差十几个G,非常奇怪,后来Google了许久才搞明白. 分析 1.linux系统内存消耗主要有三个 ...

  7. java 查看堆外内存占用_如何监控和诊断JVM堆内和堆外内存使用?

    上一讲我介绍了 JVM 内存区域的划分,总结了相关的一些概念,今天我将结合 JVM 参数.工具等方面,进一步分析 JVM 内存结构,包括外部资料相对较少的堆外部分. 今天我要问你的问题是,如何监控和诊 ...

  8. python 字典操作 内存占用_关于 python 的 dict 的内存占用问题....

    最近处理数据,有一个地方用的字典储存的,不知道为啥,有个地方的内存占用看不懂啊: 这是第一种: 31 83.4 MiB 0.0 MiB @profile 32 def main(): 33 # dat ...

  9. idea2020显示内存占用_【解决讨论】关于macbook pro 16使用 idea2020.1风扇狂转的问题(很吵)...

    有没有重度使用idea的童靴,这个夏天你们的mp16热嘛,每次写完代码之后编译的时候,电脑风扇狂转,风噪音特别大,很吵的那种,一看cpu idea占用1000多以上,温度达到80℃左右. 目前摸索的解 ...

最新文章

  1. mysql字段值后面有隐形字符_MySQL 隐形索引
  2. 牛客练习赛43 f Tachibana Kanade Loves Game
  3. bootstrap操作mysql数据库_前后端连接(BootStrap_MySQL_MyEclipse)
  4. linux ls只显示文件名或者文件夹名
  5. 数字孪生技术从概念走向实际应用
  6. IOS基础之NSFounation框架的NSDictionary,NSMutableDictionary的使用
  7. Ubuntu桌面培训(Ubuntu Desktop Course)中文译本发布
  8. 经验也有捷径,来看下这些热点、经验、技术等干货应有尽有的公众号吧!
  9. CUDA并行计算 | CUDA算法效率提升关键点概述
  10. 《剑指Offer》题目:合并两个排序的链表
  11. [渝粤教育] 西南科技大学 液压与气压传动 在线考试复习资料(1)
  12. linux b类地址设24位掩码,CIDR,子网掩码以及划分子网超网
  13. 研究生能合作发表论文吗?
  14. tds3014 自动测试软件,TDS3014 Tektronix TDS3014C
  15. 用PYTHON做一个动态钟表
  16. 预告:无穷小微积分改版,寻找接班人
  17. 小学信息技术用计算机作文,小学二年级信息技术学年总结
  18. 【说透区块链系列】区块链有哪些常见误区?
  19. 网页403是怎么回事?网页403的原因和解析。
  20. python图像识别马路_使用Python和OpenCV在道路上找到车道线

热门文章

  1. 两台域控服务器数据无法同步,主备域控数据无法同步
  2. mmall商城购物车模块总结
  3. 修改Windows中文用户名为英文(更全面的方法)
  4. VisionPro + C#:联合c#进行简易二次开发
  5. 劳务人员造成或遭受第三方损害,责任分析
  6. ftok函数 php,php坑:ftok
  7. table中加表单元素每行怎么验证_Validform 一行代码搞定整站的表单验证 - 文章
  8. kafka和pulsar的区别
  9. 001-006 Rust死灵书笔记之非安全编程与数据布局
  10. 图片的尺寸和分辨率有什么关系?