几周前我曾提到,我被项目组分配去作了一些探究linux下内存管理机制的活儿。由于咱们的产品遇到了一些与之相关的“诡异”问题。这些问题以及相关状况能够归纳以下: html
以上的描述都是基于客观事实。而我探索的主要手段,就是根据这些事实搜索互联网(google/百度)。几天下来收获颇丰。下面总结一些收获。 linux
咱们正在开发的类数据库系统有一个内存模块,出现了一个疑似”内存泄露”问题,现象以下:内存模块的内存释放之后没有归还操做系统,好比内存模块占用的内存为10GB,释放内存之后,经过TOP命令或者/proc/pid/status查看占用的内存有时仍然为10G,有时为5G,有时为3G, etc,内存释放的行为不肯定。 git
glibc使用了ptmalloc做为其内存管理器的实现。关于ptmalloc到底是如何管理内存的,我看了不少教程,其中这篇 我认为讲得最通透,想了解真相的同窗推荐去那里看。下面是给本身作的潦草总结,不适合做为学习读物(截图都是link过来的)。 github
![]()
![]()
- brk分配的内chunk list,只能从top开始线性向下释放。释放掉中间的chunk,没法归还给OS,而是并链入到了bins/fast bins的容器中。
- mmap分配的内存,等因而直接从物理内存中映射了一块过来。释放这块内存时,能够直接归还给OS。
- 对于reqest的一块内存,究竟是由brk分配,仍是由mmap分配,这是由glibc策略机制决定的。
- 有个threshold,能够调节这种策略。默认下,小于128kb由brk分配,大于等于则由mmap分配。
- 但现代的glibc实现中(还没调查从哪一个版本开始),支持了动态调节threshold技术。默认下,在64位系统上,brk能够动态调整到从128kb到32mb。调整策略基本能够归纳为:发现对顶能够release的可用内存超过256kb的话,就将threshold调整到256kb。依次类推直到32mb.
- 这个threshold也是能够人为控制的。具体见下面的连接。
- 以上几点我写了一个小程序进行过验证,发现的确如此。测试的内容大概为,用一个双向链表(std::deque)装载设计过的chuck,根据指令,要么为尾端压入一个chunk, 要么从尾端弹出一个chunk,要么从首端弹出一个chunk,观察内存用量。发现,对于小size的chunk,从尾端弹出元素后,内存均可以释放,但从首端弹出的chunk,内存并无释放;若是chunk足够大,不管从尾端仍是首端,内存均可以释放。
glibc使用如此的两种机制管理用户程序的内存,是有意设计使然。毕竟,与系统底层通讯的代价是昂贵的,若是动辄就直接操纵大量小块内存,就至关于频繁地与系统调用进行通讯,这样显然会下降程序的运行效率。将小块内存放入brk维护的一个堆中,就至关于实现了一块缓存(cache),用完了能够先攒起来,到时候能够一块儿归还给系统。公正地讲,这种设计挺smart的。 sql
但是,它尚未smart得足够好。首先,因为它的实现相对来讲仍是比较简单,只维护了堆顶的一个指针。所以想要归还给系统的话,必须从顶向下,依次归还。想象一下这种状况,假如堆顶有块内存一直被占用着,而下面的全部内存都已经没用了。那下面的这些内存,能够归还给系统吗?很遗憾,这种设计决定了答案是不能够。这就出现了“洞(Hole)”的问题。 数据库
另外,这种设计对一些因为业务需求,频繁申请/释放小块内存的用户程序而言,也不够友好。像咱们的这种3D软件,正是典型的一种状况:一个巨大的几何体,其实是由成千上万的小面片组成的,每个都不大,就是数量多。因此咱们的软件就会面临“已经释放了内存,但却没有归还给系统”的诡异问题。对付这种问题,最佳的策略,应该是早期就精心设计并使用一种适合咱们软件的“专用内存池”技术,申请连续的大块内存空间,手动”切割“开给众多小面片使用。到时候根据状况再分批归还给系统。总之,专门设计本身的内存管理方案总归是灵活多变的,能够视项目的需求状况而打造。 小程序
话说回来, 虽然glibc制定了这种有些“强硬”的内存管理方案,但也提供了一些方法容许调节相关阈值(threshold),咱们虽然不能干涉怎么管理内存,但好歹能够经过这些方法,决定“多大算大,多小算小”以及“攒到多少就归还”等这类问题。 windows
mallopt是一个专门调节相关阈值的函数,具体细节就不讲了,man手册上说得就挺明白的。下面贴的一段仍是留给本身的。想了解详情的同窗请点这里。 数组
#include < malloc.h > 缓存
int mallopt(int param, int value);
M_MMAP_THRESHOLD
For allocations greater than or equal to the limit specified (in bytes) by M_MMAP_THRESHOLD that can't be satisfied from the free list, the memory-allocation functions employ mmap(2) instead of increasing the program break using sbrk(2).
Allocating memory using mmap(2) has the significant advantage that the allocated memory blocks can always be independently released back to the system. (By contrast, the heap can be trimmed only if memory is freed at the top end.) On the other hand, there are some disadvantages to the use of mmap(2): deallocated space is not placed on the free list for reuse by later allocations; memory may be wasted because mmap(2) allocations must be page-aligned; and the kernel must perform the expensive task of zeroing out memory allocated via mmap(2). Balancing these factors leads to a default setting of 128*1024 for the M_MMAP_THRESHOLD parameter.
The lower limit for this parameter is 0. The upper limit is DEFAULT_MMAP_THRESHOLD_MAX: 5121024 on 32-bit systems or 410241024sizeof(long) on 64-bit systems.
Note: Nowadays, glibc uses a dynamic mmap threshold by default. The initial value of the threshold is 128*1024, but when blocks larger than the current threshold and less than or equal to DEFAULT_MMAP_THRESHOLD_MAX are freed, the threshold is adjusted upwards to the size of the freed block. When dynamic mmap thresholding is in effect, the threshold for trimming the heap is also dynamically adjusted to be twice the dynamic mmap threshold. Dynamic adjustment of the mmap threshold is disabled if any of the M_TRIM_THRESHOLD, M_TOP_PAD, M_MMAP_THRESHOLD, or M_MMAP_MAX parameters is set.
malloc_trim()是一个颇有意思的函数。“有意思”在我到如今还不是很明白它究竟是怎么工做的。这里也是我很想向各位请教的地方(若有看法,请不吝赐教)。根据man手册的解释,它应该是负责告诉glibc在brk维护的堆队列中,堆顶留下多少的空余空间(free space),其余往上的空余空间所有归还给系统。并且手册明确说明,它不能归还除堆顶以外的内存。下面贴一段man手册的官方描述:
The malloc_trim() function attempts to release free memory at the top of the heap (by calling sbrk(2) with a suitable argument).
The pad argument specifies the amount of free space to leave untrimmed at the top of the heap. If this argument is 0, only the minimum amount of memory is maintained at the top of the heap (i.e., one page or less). A nonzero argument can be used to maintain some trailing space at the top of the heap in order to allow future allocations to be made without having to extend the heap with sbrk(2).
按照描述所说,malloc_trim(0)应该只是归还堆顶上所有的空余内存给系统,按道理,它不该该会有能力归还堆顶下面的那些空余内存(那些“洞”)。不过,我本身作的小程序实验中,却推翻了这个论断。当我调用了malloc_trim(0)之后,我发现堆中所有的空余内存所有被归还给系统了,包括那些洞。不过,free list bing/fast bin中依然维护着这些内存地址,当再次须要申请小内存块时,老是前面的洞被再次从系统中“要”回来,而后分给调用者。这一点显得malloc_trim(0)很高级,我固然也很欢迎它具备这样出色的表现。但由于这样的行为与官方的手册描述有出入,让我理解起这个模型来至关困惑,真是百思不得姐...
我作实验的平台是Linux RH5。代码也贴了出来(写得很烂)。考虑到贴在这里会显得很臃肿,我把它分享在这里。注意这个版本中已经把双向链表替换成了静态数组,纯粹是为了作实验,效果是同样的。