微信公众号开发 webstorm汉化包 mAPI forms image soap cmd clojure 百度seo关键词优化 郑州小程序公司 网赚教程下载 matlab定义空矩阵 linux管道符 python开发安卓应用 linux撤销 css鼠标悬浮样式 java二维数组赋值 python的数据类型 python创建对象 python调用命令行 java编程入门 java正则表达 java的继承 java替换字符 java获取当前时间 java索引 java代码 java读取文件 sql实例 战地联盟辅助 安卓刷机精灵 怎么设置迅雷为默认下载器 R语言初学者指南 mac版matlab 网红照片男 快手规则 淘宝抽奖活动 jpg格式转换器 方正徐静蕾字体 文件压缩工具
当前位置: 首页 > 学习教程  > 编程语言

《Linux性能优化实战》笔记(十一)—— 内存泄漏定位与处理

2020/9/19 14:46:36 文章标签:

一、 内存的分配和回收

前面讲进程的内存空间时,我曾经提到过,用户空间内存包括多个不同的内存段,比如只读段、数据段、堆、栈以及文件映射段等,这些内存段正是应用程序使用内存的基本方式。参考《Linux性能优化实战》笔记(八)—— 内存是怎么工作的

举个例子,你在程序中定义了一个局部变量,比如一个整数数组 int data[64] ,就定义了一个可以存储 64 个整数的内存段。由于这是一个局部变量,它会从中分配内存。栈内存由系统自动分配和管理,一旦程序运行超出了这个局部变量的作用域,栈内存就会被系统自动回收,所以不会产生内存泄漏的问题。

再比如,很多时候,我们事先并不知道数据大小,就要用到标准库函数 malloc() _,_ 在程序中动态分配内存。这时候,系统就会从中分配内存。堆内存由应用程序自己来分配和管理。除非程序退出,否则这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free() 来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。

那么,其他内存段是否也会导致内存泄漏呢?经过我们前面的学习,这个问题并不难回答。

  • 只读段,包括程序的代码和常量,由于是只读的,不会再去分配新的内存,所以也不会产生内存泄漏。
  • 数据段,包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。
  • 文件映射段,包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。所以,如果程序在分配后忘了回收,就会导致跟堆内存类似的泄漏问题。

内存泄漏的危害非常大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用。内存泄漏不断累积,甚至会耗尽系统内存。虽然,系统最终可以通过 OOM (Out of Memory)机制杀死进程,但进程在 OOM 前,
可能已经引发了一连串的反应,导致严重的性能问题。

内存泄漏的危害这么大,那我们应该怎么检测这种问题呢?特别是,如果你已经发现了内存泄漏,该如何定位和处理呢。

 

二、 案例

接下来,我们就用一个计算斐波那契数列的案例,来看看内存泄漏问题的定位和处理方法。

斐波那契数列是一个这样的数列:0、1、1、2、3、5、8…,除了前两个数是 0和1,其他数都由前面两数相加得到,用数学公式来表示就是 F(n)=F(n-1)+F(n-2),其中n>=2,F(0)=0, F(1)=1。

运行应用程序,同时运行下面的 vmstat ,等待一段时间,观察内存的变化情况。如果忘了 vmstat 里各指标的含义,记得复习前面内容,或者执行 man vmstat 查询。

从输出中你可以看到,内存的 free 列在不停的变化,并且是下降趋势;而 buffer 和 cache基本保持不变。

未使用内存在逐渐减小,而 buffer 和 cache 基本不变,这说明,系统中使用的内存一直在升高。但这并不能说明有内存泄漏,因为应用程序运行中需要的内存也可能会增大。比如说,程序中如果用了一个动态增长的数组来缓存计算结果,占用内存自然会增长。

那怎么确定是不是内存泄漏呢?或者换句话说,有没有简单方法找出让内存增长的进程,并定位增长内存用在哪儿呢?

这里,我介绍一个专门用来检测内存泄漏的工具,memleak。memleak 可以跟踪系统或指定进程的内存分配、释放请求,然后定期输出一个未释放内存和相应调用栈的汇总情况(默认 5 秒)。

memleak 是 bcc 软件包中的一个工具(上一节装好了),执行/usr/share/bcc/tools/memleak 就可以运行它。

# -a 表示显示每个内存分配请求的大小以及地址
# -p 指定案例应用的PID号
$ /usr/share/bcc/tools/memleak -a -p $(pidof app)

从 memleak 的输出可以看到,案例应用在不停地分配内存,并且这些分配的地址没有被回收。

这里有一个warning,Couldn’t find .text section in /app,所以调用栈不能正常输出,最后的调用栈部分只能看到 [unknown] 的标志。为什么会有这个错误呢?实际上,这是由于案例应用运行在容器中导致的。memleak 工具运行在容器之外,并不能直接访问进程路径 /app。

比方说,在终端中直接运行 ls 命令,你会发现,这个路径的确不存在:

类似的问题,我在 perf 使用方法中已经提到好几个解决思路。最简单的方法,就是在容器外部构建相同路径的文件以及依赖库。这个案例只有一个二进制文件,所以只要把案例应用的二进制文件放到 /app 路径中,就可以修复这个问题。

比如,你可以运行下面的命令,把 app 二进制文件从容器中复制出来,然后重新运行memleak 工具:

这一次,我们终于看到了内存分配的调用栈,原来是 fibonacci() 函数分配的内存没释放。

定位了内存泄漏的来源,下一步自然就应该查看源码,想办法修复它。

你会发现, child() 调用了 fibonacci() 函数,但并没有释放 fibonacci() 返回的内存。所以,想要修复泄漏问题,在 child() 中加一个释放函数就可以了,比如:

运行修复后的代码,再验证一下内存泄漏是否修复:

# 清理原来的案例应用
$ docker rm -f app
# 运行修复后的应用
$ docker run --name=app -itd feisky/app:mem-leak-fix
# 重新执行 memleak工具检查内存泄漏情况
$ /usr/share/bcc/tools/memleak -a -p $(pidof app)
Attaching to pid 18808, Ctrl+C to quit.
[10:23:18] Top 10 stacks with outstanding allocations:
[10:23:23] Top 10 stacks with outstanding allocations:

现在,我们看到,案例应用已经没有遗留内存,证明我们的修复工作成功完成。


本文链接: http://www.dtmao.cc/news_show_200169.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?