极客时间——Linux内核技术实战课:内存泄漏问题
目录:
内存泄漏问题
进程的那些内存类型容易引起内存泄漏
应用程序会直接或者间接调用系统调用,例如brk(2)和sbrk(2)修改的是heap(堆),mmap(2)和munmap(2)修改的是Memory Mapping Region(内存映射区)
应用程序针对的都是虚拟地址,虚拟内存地址被转换为物理地址进行操作
内存可以分为四类
- 私有匿名内存:进程的堆,栈以及mmap
(MAP_ANON | MAP_PRIVATE)
申请的内存,栈是由操作系统管理,堆和私有匿名映射是由程序管理,容易产生内存泄漏 - 共享匿名内存:
mmap(MAP_ANON | MAP_SHARED)
申请的内存,例如tmpfs和shm - 私有文件映射:
mmap(MAP_FILE | MAP_PRIVATE)
申请的内存,例如共享库和可执行的代码段映射到内存空间 - 共享文件映射:
mmap(MAP_FILE | MAP_SHARED)
申请的内存,page cache就是这种内存
请求内存的过程是CPU将虚拟内存地址传给内存管理单元MMU,MMU在高速缓存TLB中查找转换关系,找到了对应内存地址直接访问,如果找不到则在地址转换表中查找
进程内存可以top->g->3查看,可以看到
VIRT RES CODE DATA SHR nMaj nDRT
这些信息来自/proc/[pid]/statm
和/proc/[pid]/stat
- VIRT是进程申请的虚拟内存大小
- RES是映射到进程地址空间的物理内存大小
- SHR是MAP_SHARED方式存储的内存,可以共享
- CODE是代码段大小
- DATA是数据段大小
- nDRT是脏页大小,2.6内核之后废弃了
RES中的一些内存是很多进程共享的
查看一个ssh进程的详细内存使用
$ pmap -x `pidof sshd`
Address Kbytes RSS Dirty Mode Mapping
000055e798e1d000 768 652 0 r-x-- sshd
000055e7990dc000 16 16 16 r---- sshd
000055e7990e0000 4 4 4 rw--- sshd
000055e7990e1000 40 40 40 rw--- [ anon ]
...
00007f189613a000 1800 1624 0 r-x-- libc-2.17.so
00007f18962fc000 2048 0 0 ----- libc-2.17.so
00007f18964fc000 16 16 16 r---- libc-2.17.so
00007f1896500000 8 8 8 rw--- libc-2.17.so
...
00007ffd9d30f000 132 40 40 rw--- [ stack ]
...
- Address是起始地址
- Kbytes是虚拟内存大小
- RSS是虚拟内存中已经分配的大小
- Dirty是内存数据未同步到磁盘的大小
- Mode是权限,r-x为可读可执行,一般是代码段,rw-为可读写,一般为数据段,r--为只读,一般为只读数据段
- Mapping是文件占用内存的文件,heap为堆,stask为栈
原理也是解析的/proc/[pid]/maps
和/proc/[pid]/smaps
$ cat /proc/meminfo
...
Cached: 3799380 kB
...
AnonPages: 1060684 kB
...
Shmem: 8724 kB
...
AnonPages为私有内存,Cached为共享内存,匿名共享内存为Shmem
如何预防内存泄漏导致的问题
内存泄漏之后,操作系统进行OOM的时候调用printk会触发多个操作
- 写入内核缓冲区,通过dmesg命令查看
- 写入rsyslog,由内核缓冲区转储到/var/log/messages
- 发往服务器的console,例如串口
console的速度非常慢,输出过多的日志,导致OOM时间过长,没有回收内存,虽然OOM会有全局的锁,但是会导致其他申请内存的时候重试。如果有很多程序申请重试,就会被阻塞在这里
可以做的vm.oom_dump_tasks
调整为0,但是OOM的日志没有打印,无法排查问题
或者避免打印到console,将console的日志级别调整为小于默认的4
# 初始配置(为7):所有信息都会输出到console
$ cat /proc/sys/kernel/printk
7 4 1 7
# 调整console_loglevel级别,不让OOM信息打印到console
$ echo "3 4 1 7" > /proc/sys/kernel/printk
# 查看调整后的配置
$ cat /proc/sys/kernel/printk
3 4 1 7
不过这样默认级别为4的内核服务也无法打印到console
进程没有消耗内存,内存到哪里去了
OOM的时候kill时候按照/proc/[pid]/oom_score
的排序进行kill,系统一般为-1000,而kubernetes的为-998
tmpfs的大小调整
$ mount -o remount,size=2G /run
进程退出malloc()在用户态申请的内存直接释放了,kmalloc()/kfree()和vmalloc()/vfree()在内核态申请,生命周期和内核一致,而不是内核模块,只有重启才会释放
/proc/meminfo中内核内存(比如VmallocUsed和SUnreclaim)过大或者一直增长就可能是内存泄漏
kmemleak可以用来分析内存泄漏,但是影响性能
内存泄漏如何查到根因
$ pidstat -r -p 31108 1
04:47:00 PM 31108 353.00 0.00 299029776 4182152 12.73 app_server
...
04:47:59 PM 31108 149.00 0.00 299029776 4181052 12.73 app_server
04:48:00 PM 31108 191.00 0.00 299040020 4181188 12.73 app_server
...
04:48:59 PM 31108 179.00 0.00 299040020 4181400 12.73 app_server
04:49:00 PM 31108 183.00 0.00 299050264 4181524 12.73 app_server
...
04:49:59 PM 31108 157.00 0.00 299050264 4181456 12.73 app_server
04:50:00 PM 31108 207.00 0.00 299060508 4181560 12.73 app_server
...
04:50:59 PM 31108 127.00 0.00 299060508 4180816 12.73 app_server
04:51:00 PM 31108 172.00 0.00 299070752 4180956 12.73 app_server
内存稳定有一分钟,VSZ会增大10244KB
$ cat /proc/31108/smaps
...
7faae0e49000-7faae1849000 rw-p 00000000 00:00 0
Size: 10240 kB
Rss: 80 kB
Pss: 80 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 80 kB
Referenced: 60 kB
Anonymous: 80 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
7faae1849000-7faae184a000 ---p 00000000 00:00 0
Size: 4 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
私有空间为rw-p,一个保护页——p进程无法访问,应该是线程栈有关系了
$ strace -t -f -p 31108 -o 31108.strace
-f跟踪线程