如何评估服务是否内存泄漏了?
0. 问题
最近业务方发布了 user-authentication 服务,但是不到一天就被 OOM killer 杀死了,从监控上看发布后内存占用在持续增加。
哪里吃内存了?
首先看 GC 日志,Java Heap 和 Metaspace 很正常,所以怀疑是堆外内存泄漏了,而这个版本加了一个 sec-crypto JNI 库,自然成了重点怀疑对象,通过看代码也很快定位到了没有 ReleaseStringUTFChars 的问题,所以很快做了修复发版。
如果没有明显的怀疑对象,如何排查堆外内存泄漏的问题?
这就很难了,如果你写过 C++就会明白内存泄漏是个持久的斗争对象,好在 Java 世界里的 JNI 库并不多,而且主流的库代码质量都比较高,即便有坑大部分也被别人踩过了。
如果实在没有头绪,可以考虑使用 jemalloc 的 allocation profiling 功能,具体操作参考这篇文章 https://www.evanjones.ca/java-native-leak-bug.html
业务方升级版本重发后,观察了几个小时,内存占用基本平稳了。大家都认为这个问题解决了,但是过了两天业务方的一个截图又让被内存泄漏支配着的恐怖回来了。
1. 排查
面对业务方提出的内存泄漏嫌疑,首先还是得看一下“证据”本身,首先看一下纵坐标的尺度:一天时间“内存使用百分比”从 55.80% 增加至 56.05%,增加 0.25%,也就是 24MB,从这个增长幅度对于一个 Java 程序来说比较难做出“泄漏”还是“正常”的判断。
1.1 寻找其它指标
再看一下“内存使用百分比”这个指标是怎么来的,该指标是 prometheus 采集的容器指标,具体的含义暂时不清楚,所以先找熟悉的指标辅助排查一下。
对于 linux 系统中的进程,操作系统提供的进程状态位于 /proc/{pid}/status 这个文件中,其中的 VmRSS 字段表征进程的物理内存占用。
从这个指标来看服务应该不存在内存泄漏。
那么为什么“内存使用百分比”这个指标却是持续增长的趋势呢?
1.2 重新理解容器的“内存使用百分比”指标
要回答这问题,首先需要理解“内存使用百分比”这个指标本身。这个指标不是容器直接提供的,而且通过 prometheus 查询计算出来的,用 container_memory_working_set_bytes 除以 container_spec_memory_limit_bytes 得出的百分比。
container_spec_memory_limit_bytes 是个明确的值,也就是 k8s containerSpec 描述中 limit,表征容器内存占用的上限。container_memory_working_set_bytes 的含义就不明确了,那么就一步一步追下去,找到这个指标数据的提供者——google/cadvisor(https://github.com/google/cadvisor)。
指标的定义位于 info/v1/container.go
通过这三行简单的注释可以直观的理解,这个指标不仅仅包含进程的内存占用。
再继续找一下这个指标是如何采集的,计算逻辑位于 container/libcontainer/handler.go
是用 Usage 减去了 total_inactive_file,这几个数据又是采集的 cgroup 提供的统计数据,Usage 读取的 /sys/fs/cgroup/memory/memory.usage_in_bytes 文件,total_inactive_file 读取的是 /sys/fs/cgroup/memory/memory.stat 文件。
要搞清楚这些指标是如何统计的就得看 linux 内核 cgroup 部分的源码了,这里就不继续追下去了,而是选择通过内核文档来理解。(内存子系统还是比较复杂,笔者是半吊子水平,之后搞明白了再单独写篇文章)
Usage 这值是个不太精确的表征内存占用的数值,其中既包含了进程占用的物理内存(RSS),又包含 CACHE。(SWAP 我们关闭了,所以暂且忽略)
CACHE 就比较复杂了,在计算 WorkingSet 时减掉的 total_inactive_file 就是 CACHE 的一部分,通俗理解也就是操作系统为了提高 IO 效率提供的文件缓存,这部分内存是用户态的进程无法控制的,操作系统可能会占用很多,即便是 inactive 了也不会立即释放,只有在容器销毁或系统内存不足时才会由操作系统回收。与之对应的还有一个 total_active_file 文件缓存,即便它是 active 的,在内存资源紧张时也可以被回收。
这里用的“内存不足”、“资源紧张”等词只是理论性描述,并不是严谨表述 linux 内核的策略。
既然是 CACHE,并且可以回收,我们把容器资源 limit 调低观察一下。
1.3 调低内存 limit 验证
可见表征进程物理内存占用的 rss 指标很快就处于了稳定状态。
而容器 usage 和 cache 指标的趋势基本一致,也就是说内存占用的增加减少基本都是缓存的增加减少,由于 usage 不能超过 limit,所以有段时间是持平的状态。working_set 指标由于包含了 total_active_file 所以也呈现上升的趋势,但是最终也达到了稳定状态。
2. 结论
内存泄漏确实是一个很可怕的事情,但是对于小幅度的内存增加也不需要那么紧张,在一个比较合适的时间窗口评估 container_memory_rss 这个指标是一个比较合适的方式。例如:短时间的持续大幅度增加,或者长时间的持续较大增加,这些可能是需要重点关注的情况。
另外需要注意的是 OOM killer 用的 container_memory_working_set_bytes 这个指标,而这个指标又包含了 total_active_file 文件缓存,虽然该部分内存并不是业务进程明确使用的,但是为了避免被 OOM killer 杀死,在申请容器配额时需要多估算几百兆的内存资源。
版权声明: 本文为 InfoQ 作者【BUG侦探】的原创文章。
原文链接:【http://xie.infoq.cn/article/2ae0800fb365f8011e9af86a9】。文章转载请联系作者。
评论