一次由于sbrk()无法压缩导致内存RSS虚高造成“内存泄露”的假象
最近写的一个C网络服务器程序在高压力后top进程看内存的RSS总是等于最高值, 以为是内存泄露,到处查看代码没问题,用valgrind跟了一下也还是没有发现问题。
于是将malloc改为自己的函数记录了一下程序的malloc, free操作,从而算出还有多少大小内存没有释放,最后结果是一切正常,基本都释放了,但奇怪的是RSS却没有降下来。
后来想了想应该是malloc/free本身还没有将其分配的内存归还给操作系统,或者说还没有来得及将堆往后退,所以导致free不能释放已经申请的内存,从而导致“内存泄露”的假象。
因为C语言glibc的内存管理,从操作系统申请内存的时候,有2种方式:sbrk() 向上线性扩展虚拟地址空间, mmap匿名内存映射文件分配虚拟地址空间; 前者对应小块内存分配,后者满足大块内存分配。
对于后者申请的大块内存分配,比较容易glibc能够及时释放,但对于sbrk()来说,情况比较复杂了。
------------------ address 0
| ---------------| <----堆开始
| |
| ---------------| <-sbrk() 网高位地址延生
| |
| |
| | <----mmap() 在中部申请的大块内存,申请一次消耗大,而且会自动填0,不适合频繁申请。
| |
|---------------| <----高位内核空间
由于brk(),sbrk()必须等到高位的内存全部释放后,才能够进行收缩,因为其必须是线性收缩的,不能像mmap那样形成空洞,也就是在sbrk()返回的地址的前面,的所有堆空间必须都已经分配给进程了。
由此可知很可能我的程序主要占用内存的地方都释放了, 但一些辅助性的功能占用了高位的地址没有释放,从而导致RSS无法缩小。这种情况其实不算内存泄露,顶多是虚拟地址空间会很大,但基本不会影响物理内存的。
可选的办法是将这部分长期不会释放的内存提前分配,而不是到系统高峰期再分配,这样就避免了持有高位sbrk返回的内存地址。
近期评论