在线笔记
全部博文(596)
分类: 其他平台
2013-08-16 15:44:41
几周前我曾提到,我被项目组分配去做了一些探究linux下内存管理机制的活儿。因为我们的产品遇到了一些与之相关的“诡异”问题。这些问题以及相关情况可以概括如下:
以上的描述都是基于客观事实。而我探索的主要手段,就是根据这些事实搜索互联网(google/百度)。几天下来收获颇丰。下面总结一些收获。
我们正在开发的类数据库系统有一个内存模块,出现了一个疑似”内存泄露”问题,现象如下:内存模块的内存释放以后没有归还操作系统,比如内存模块占用的内存为10GB,释放内存以后,通过TOP命令或者/proc/pid/status查看占用的内存有时仍然为10G,有时为5G,有时为3G, etc,内存释放的行为不确定。
glibc使用了ptmalloc作为其内存管理器的实现。关于ptmalloc究竟是如何管理内存的,我看了很多教程,其中这篇 我认为讲得最通透,想了解真相的同学推荐去那里看。下面是给自己做的潦草总结,不适合作为学习读物(截图都是link过来的)。
- 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的。
可是,它还没有smart得足够好。首先,由于它的实现相对来说还是比较简单,只维护了堆顶的一个指针。因此想要归还给系统的话,必须从顶向下,依次归还。想象一下这种情况,假如堆顶有块内存一直被占用着,而下面的所有内存都已经没用了。那下面的这些内存,可以归还给系统吗?很遗憾,这种设计决定了答案是不可以。这就出现了“洞(Hole)”的问题。
另外,这种设计对一些由于业务需求,频繁申请/释放小块内存的用户程序而言,也不够友好。像我们的这种3D软件,正是典型的一种情况:一个巨大的几何体,实际上是由成千上万的小面片组成的,每一个都不大,就是数量多。所以我们的软件就会面临“已经释放了内存,但却没有归还给系统”的诡异问题。对付这种问题,最佳的策略,应该是早期就精心设计并使用一种适合我们软件的“专用内存池”技术,申请连续的大块内存空间,手动”切割“开给众多小面片使用。到时候根据情况再分批归还给系统。总之,专门设计自己的内存管理方案总归是灵活多变的,可以视项目的需求情况而打造。
话说回来, 虽然glibc制定了这种有些“强硬”的内存管理方案,但也提供了一些方法允许调节相关阈值(threshold),我们虽然不能干涉怎么管理内存,但好歹可以通过这些方法,决定“多大算大,多小算小”以及“攒到多少就归还”等这类问题。
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。代码也贴了出来(写得很烂)。考虑到贴在这里会显得很臃肿,我把它分享在。注意这个版本中已经把双向链表替换成了静态数组,纯粹是为了做实验,效果是一样的。
tssqkkcom2014-12-18 03:23:40
<div id="sina_keyword_ad_area2" class="articalContent ">
<div> <wbr>
<p><b>旅行社管理条例</B></P>
<p>
<wbr>(1996年10月15日中华人民共和国国务院令第205号发布</P>
<p>
根据2001年12月11日《国务院关于修改〈旅行社管理条例〉的决定》修订)</P>
<p>目 <wbr> 录</P>
<p>第一章 总 <wbr> 则</P>
<p>第二章 旅行社设立</P>
<p>第三章 旅行社经营</P>
<p>第四章 外商投资旅行社的特别规定<