linux隐式内存泄露,「Linux」- 内存泄漏(学习笔记)

「Linux」- 内存泄漏(学习笔记)

更新日期:2020年08月05日

@IGNORECHANGE

对应用程序来说,动态内存的分配和回收,是既核心又复杂的一个逻辑功能模块。

管理内存的过程中,也很容易发生各种各样的“事故”:

1)没正确回收分配后的内存,导致内存泄漏;

2)访问的是已分配内存边界外的地址,导致程序异常退出;

内存泄漏是如何产生的

用户空间内存包括多个不同的内存段,通常堆内存与内存映射段容易产生内存泄漏。内核空间我们不需要担心(当然可能存在内存泄漏),我们这里关注的重点是用户空间的应用程序产生的内存泄漏,已经如何处理。

只读段

包括程序的代码和常量,由于是只读的,不会再去分配新的内存,所以也不会产生内存泄漏。

数据段

包括全局变量和静态变量,这些变量在定义时就已经确定了大小,所以也不会产生内存泄漏。

栈内存(Stack)

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

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

堆内存(Heap)

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

堆内存由应用程序自己来分配和管理,如果应用程序没有正确释放堆内存,就会造成内存泄漏。

1)程序退出,系统自动释放内存

2)或者需要应用程序明确调用库函数 free() 来释放它们

内存映射段

包括动态链接库和共享内存

其中共享内存由程序动态分配和管理。所以如果程序在分配后忘了回收,就会导致跟堆内存类似的泄漏问题。

内存泄漏的危害

内存泄漏会带来系列问题:

1)应用程序自己不能访问:应用程序已经忘记自己申请的内存,导致内存被“占用但闲置”;

2)系统也不能把它们再次分配给其他应用:对应的物理内存被占用,导致无法再分配;

3)内存泄漏不断累积,甚至会耗尽系统内存;

虽然操作系统会通过 OOM - Out of Memory 会结束进程,但是在这之前依旧会带来其他问题:

1)其他需要内存的进程,可能无法分配新的内存;

2)内存不足,又会触发系统的缓存回收以及 SWAP 机制,从而导致 I/O 性能问题;

问题排查:检查内存泄漏(memleak)

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

# docker logs app

// 如何定位内存泄漏问题

// 无法使用 top ps 等工具,但是可以使用 vmstat 查看内存增长情况

# vmstat

0 0 15777696 297236 51480 1548608 47 1135 243 1417 6364 15045 26 6 67 0 0

0 0 15777696 278084 51628 1543464 63 0 144 160 5876 15071 29 4 66 0 0

1 0 15777696 273948 51644 1544536 28 0 28 16 5440 14589 25 5 70 0 0

1 0 15777184 269524 51796 1545372 39 0 128 57 6214 16156 27 5 67 0 0

1 0 15776928 389988 51820 1544004 41 0 88 115 13738 14626 32 5 62 0 0

0 0 15776928 383908 51828 1545032 16 0 17 327 5702 15272 26 5 69 0 0

3 0 15776928 429984 51848 1545208 51 0 132 359 6342 16245 32 5 62 0 0

procs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------

r b swpd free buff cache si so bi bo in cs us sy id wa st

5 0 15776928 427984 51876 1545644 4 0 5 44 6154 14798 28 6 66 0 0

0 0 15776928 413108 51896 1545164 43 0 45 129 5919 14790 29 5 66 1 0

1 0 15776672 397232 52056 1551576 135 0 839 259 6042 15907 26 6 68 0 0

2 0 15776160 396176 52064 1549036 25 0 27 11 5515 14400 24 5 71 0 0

0 0 15776160 381812 52088 1548952 27 0 31 183 5672 14403 29 5 66 0 0

// 但是效果也不是很明显,可使用内存反而越来越多了,而 buff 与 cache 变化不大

// 还有一个麻烦的方法,用 top 或 ps 来观察进程的内存使用情况,然后找出内存使用一直增长的进程,最后再通过 pmap 查看进程的内存分布

// 我们还可以使用 memleak 检测内存泄漏

// memleak 好像要比 valgrind 进行内存泄漏检测要方便很多

# memleak-bpfcc -a -p $(pidof app)

Attaching to pid 638, Ctrl+C to quit.

[11:47:50] Top 10 stacks with outstanding allocations:

addr = 7f74780d75e0 size = 8192

addr = 7f74780d55d0 size = 8192

addr = 7f74780d95f0 size = 8192

addr = 7f74780db600 size = 8192

32768 bytes in 4 allocations from stack

fibonacci+0x1f [app]

child+0x4f [app]

start_thread+0xdb [libpthread-2.27.so]

[11:47:55] Top 10 stacks with outstanding allocations:

addr = 7f74780dd610 size = 8192

addr = 7f74780d75e0 size = 8192

addr = 7f74780d55d0 size = 8192

addr = 7f74780d95f0 size = 8192

addr = 7f74780db600 size = 8192

addr = 7f74780e3640 size = 8192

addr = 7f74780df620 size = 8192

addr = 7f74780e1630 size = 8192

addr = 7f74780e5650 size = 8192

73728 bytes in 9 allocations from stack

fibonacci+0x1f [app]

child+0x4f [app]

start_thread+0xdb [libpthread-2.27.so]

// 从调用堆栈中可以看出是 fibonacci() 函数分配的内存没释放

// 如果显示 [unknown] 则是因为 memleak 没有找到程序文件,因此无法找到符号表

// 至于修复,这里不再展开,需要查看源码以定位问题。

1)先要确认内存是否被缓存 / 缓冲区占用,排除缓存 / 缓冲区

2)继续用 pidstat 或者 top,定位占用内存最多的进程

3)通过 vmstat 或者 sar 发现内存在不断增长后,可以分析中是否存在内存泄漏的问题

4)使用内存分配分析工具 memleak ,检查是否存在内存泄漏

问题排查:CentOS 7.4, bind-sdb-9.9.4-74.el7_6.1.x86_64, valgrind

关于内存泄漏问题总结

实际应用程序就复杂多了。比如说:

1)malloc() 和 free() 通常并不是成对出现,在每个异常处理路径和成功路径上都需要释放内存;

2)在多线程程序中,一个线程中分配的内存,可能会在另一个线程中访问和释放

3)更复杂的是,在第三方的库函数中,隐式分配的内存可能需要应用程序显式释放

Java

如果是 java 应用程序,Java 看到的是JVM 的堆栈。其实,jmap这些Java原生的工具更好用

对java进程可以先通过 jstat -gc pid 1s 每隔1s 查看当前进程gc情况,如果存在内存泄露的话,那么该对象存活时间会很长当然会晋升到老年代,所以通过看老年代变化趋势,如果增大的话,我们再使用jmap -histo:live pid 查看进程中heap区对象的个数和占用的空间大小,找出数量大的对象然后找到对应的类查看代码,是否会存在内存泄露问题。

注意:可以将jmap -histo:live pid > data.txt 导入到一个文件中,然后通过sort 根据对象个数或占用空间进行排序

ThreadLocal使用不当会导致内存泄露

当给线程池中的线程设置local值 threadLoacl.set(obj) 后没有通过 threadLoacl.remove()就会导致内存泄露

根据ThreadLocal实现代码上看,每个线程中都会有个ThreadLocalMap 这个map中的key为ThreadLocal对象,value就是对应set的值,当离开了作用域threadLocal就不会再指向ThreadLocal对象,由于ThreadLocalMap中的key为WeakReference 当该对象只有它自己指向时就会导致key变成了null,如果当前线程是在线程池中是会一直存活的,也就是map中的value值会一直指向堆的对象,从而导致了内存泄露

参考文献

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值