Linux 内存泄漏了,我该如何定位和处理?

对普通进程来说,能看到的其实是内核提供的虚拟内存,这些虚拟内存还需要通过页表,由系统映射为物理内存。

当进程通过 malloc()申请虚拟内存后,系统并不会立即为其分配物理内存,而是在首次访问时,才通过缺页异常陷入内核中分配内存。

为了协调 CPU与磁盘间的性能差异,Linux 还会使用Cache和Buffer,分别把文件和磁盘读写的数据缓存到内存中。

对应用程序来说,动态内存的分配和回收,是既核心又复杂的一个逻辑功能模块。管理内存的过程中,也很容易发生各种各样的"事故",比如

  •  没正确回收分配后的内存,导致了泄漏。
  • 访问的是已分配内存边界外的地址,导致程序异常退出,等等。

今天带你来看看,内存泄漏到底是怎么发生的,以及发生内存泄漏之后该如何排查和定位。说起内存泄漏,这就要先从内存的分配和回收说起了。内存的分配和回收

 

内存的分配与回收(程序需要哪些内存,哪些内存容易泄露)


先回顾一下,你还记得应用程序中,都有哪些方法来分配内存吗?用完后,又该怎么释放还给系统呢?

前面讲进程的内存空间时,我曾经提到过,用户空间内存包括多个不同的内存段,比如只读段、数据段、堆、栈以及文件映射段等。这些内存段正是应用程序使用内存的基本方式。

举个例子,你在程序中定义了一个局部变量,比如一个整数数组 int data[64],就定义了一个可以存储64个整数的内存段。由于这是一个局部变量,它会从内存空间的栈中分配内存

栈内存由系统自动分配和管理。一旦程序运行超出了这个局部变量的作用域,栈内存就会被系统自动回收,所以不会产生内存泄漏的问题。

再比如,很多时候,我们事先并不知道数据大小,所以你就要用到标准库函数malloc(),在程序中动态分配内存。这时候,系统就会从内存空间的堆中分配内存。

堆内存由应用程序自己来分配和管理。除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。

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

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

内存泄漏的危害非常大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用。内存泄漏不断累积,甚至会耗尽系统内存。

虽然,系统最终可以通过OOM(Out of Memory)机制杀死进程,但进程在OOM前,可能已经引发了一连串的反应,导致严重的性能问题。比如,其他需要内存的进程,可能无法分配新的内存,内存不足,又会触发系统的缓存回收以及SWAP机制,从而进一步导致 I/O的性能问题等等。

比如,其他需要内存的进程,可能无法分配新的内存,内存不足,又会触发系统的缓存回收以及SWAP机制从而进一步导致 I/O的性能问题等等。

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

接下来,我们就用一个计算斐波那契数列的案例,来看看内存泄漏问题的定位和处理方法。斐波那契数列是一个这样的数列∶0、1、1、2、3、5、8也就是除了前两个数是0和1,其他数都由前面两数相加得到,用数学公式来表示就是F(n)=F(n-1)+F(n-2),(n>=2),F(O)=0, F(1)=1。

 

 

案例(镜像包可以直接下载,每个人都可以去拉取这个镜像)


 执行下面的命令来运行案例:

$ docker run --name=app -itd feisky/app:mem-leak

 案例成功运行后,你需要输入下面的命令,确认案例应用已经正常启动。如果一切正常,你应该可以看到下面这个界面:

$ docker logs app
2th => 1
3th => 2
4th => 3
5th => 5
6th => 8
7th => 13

从输出中,我们可以发现,这个案例会输出斐波那契数列的一系列数值。实际上,这些数值每隔 1 秒输出一次。

知道了这些,我们应该怎么检查内存情况,判断有没有泄漏发生呢?你首先想到的可能是 top 工具,不过,top 虽然能观察系统和进程的内存占用情况,但今天的案例并不适合。内存泄漏问题,我们更应该关注内存使用的变化趋势。

所以,开头我也提到了,今天推荐的是另一个老熟人, vmstat 工具。

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

# 每隔3秒输出一组数据
$ vmstat 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 6601824  97620 1098784    0    0     0     0   62  322  0  0 100  0  0
0  0      0 6601700  97620 1098788    0    0     0     0   57  251  0  0 100  0  0
0  0      0 6601320  97620 1098788    0    0     0     3   52  306  0  0 100  0  0
0  0      0 6601452  97628 1098788    0    0     0    27   63  326  0  0 100  0  0
2  0      0 6601328  97628 1098788    0    0     0    44   52  299  0  0 100  0  0
0  0      0 6601080  97628 1098792    0    0     0     0   56  285  0  0 100  0  0 

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

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

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

根据前面内容,你应该想到了用 top 或 ps 来观察进程的内存使用情况,然后找出内存使用一直增长的进程,最后再通过 pmap 查看进程的内存分布。

但这种方法并不太好用,因为要判断内存的变化情况,还需要你写一个脚本,来处理 top 或者 ps 的输出。

这里,我介绍一个专门用来检测内存泄漏的工具,memleak。

memleak 可以跟踪系统或指定进程的内存分配、释放请求,然后定期输出一个未释放内存和相应调用栈的汇总情况(默认 5 秒)。 

Linux处理内存泄漏的方法有多种。首先,可以使用内存泄漏检测工具来帮助定位和解决问题。一些常用的内存泄漏检测工具包括mtrace、memwatch、valgrind和debug_new。这些工具可以帮助开发人员找到内存泄漏的位置和原因,从而进行修复。\[1\] 另外,对于隐式内存泄漏,需要特别注意。隐式内存泄漏指的是程序在运行过程中不停地分配内存,但直到结束时才释放内存。虽然最终程序释放了所有申请的内存,但对于长时间运行的服务器程序来说,不及时释放内存可能导致系统内存耗尽。因此,需要及时释放内存以避免隐式内存泄漏。\[2\] 此外,还需要注意一次性内存泄漏和偶发性内存泄漏。一次性内存泄漏指的是只会发生一次且仅有一块内存发生泄漏的情况,例如在一个Singleton类的构造函数中分配内存,在析构函数中没有释放该内存。而偶发性内存泄漏是由于算法上的缺陷导致的,可能会导致内存泄漏。对于这些情况,需要仔细检查代码并确保正确释放内存。\[3\] 综上所述,处理Linux内存泄漏的方法包括使用内存泄漏检测工具、及时释放内存以避免隐式内存泄漏,并注意一次性内存泄漏和偶发性内存泄漏的情况。 #### 引用[.reference_title] - *1* *2* *3* [Linux内存泄漏检查工具](https://blog.csdn.net/weixin_42275611/article/details/105826846)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值