PostgreSQL中的内存管理

最近参与一个跟postgresql相关的开发,因项目需要有对工程中内存泄漏的问题做过一些调查,研究了一下postgresql内存的管理机制,觉得这篇博文写的很好,转来做个分享微笑

转自 酒中仙点击打开链接

本文将介绍PostgreSQL中独特的内存管理,它一般根据分配块的大小,决定如何分配,如果相对较大的块,进行直接分配(调用malloc),如果相对较小的块,则是可能在已经分配的空间里面取出(不需要调用malloc)。而且在内存释放的时候,小的内存块不用释放,只有大的内存块才需要调用free操作。如此情况下,内存的分配与释放会比较快。

接下来进行详细的分析,首先给出它的数据结构。

typedef struct AllocSetContext
{
   MemoryContextDataheader;   暂时不用管该域
   
   AllocBlockData*blocks;           
   AllocChunkData*freelist[ALLOCSET_NUM_FREELISTS];       (ALLOCSET_NUM_FREELISTS==11)
   bool       isReset;       
   
   Size       initBlockSize;   
   Size       maxBlockSize;   
   Size       nextBlockSize;   
   Size       allocChunkLimit;   
   AllocBlock   keeper;           
} AllocSetContext;

其中最重要的两个部分是blocks链表头和freelist数组,其中freelist数组是空闲块链表数组,同一个空闲块链表中块大小相同,不同空闲块链表大小不相同,从8Byte(索引为0)开始,每次往后移动一个位置,大小变成原来的两倍,所以最大为8K。(上述的值是系统中定义的,可以更改)

在详细分析之前,先看看AllocBlock和AllocChuck相关的数据结构

typedef struct AllocBlockData
{
   AllocSetContext*aset;           
   AllocBlockData*next;           
   char      *freeptr;       
   char      *endptr;           
} AllocBlockData;

可以看出,在每个BlockData(大内存块的头部)中有指向空闲位置的指针,该内存块尾部的指针,指向下一个内存块Block的指针,对于AllocSetContext中的blocks链表中,只有第一个块是含有空闲块的,其他的块的空闲空间移到了freelist中去。(说明:为了区分,在下面的说明中,AllocBlockData称为大块,AllocChunkData称为小块,大块里面包含一个或者数个小块)

typedef struct AllocChunkData
{
   
   void      *aset;
   
   Size       size;
} AllocChunkData;

对于第一个域有两个用途,如果是空闲的,未分配出去的块,它在freelist数组中对应大小的块链上,此时aset相当于大块中的next。如果是被分配出去的,则指向了拥有该内存块的Context。所有的分配的内存块(小块),都是从大块中分配得到的,所以小块前面都会有这个头部,它会在free时会被用到,因为对于free来说,只有一个指向要被删除块的指针,而通过块的起始指针找到块的头部,相当于传递了拥有该内存块的Context,便可以针对该context进行free操作。

下面给出分配ContextAlloc流程:

输入:分配块的大小

输出:对应的内存块

image

 

这里的context指的是AllocSetContext,块是否太大依据freelist中最大的块大小(PostgreSQL中为8K)来划分的。除了第一步中太大时候的情况,其他的分配内存的大小都是2的幂。对于红色字体部分的分配块的大小是按照AllocSetContext中的nextblockSize和需求空间(转换成2的幂)的较小值进行的,nextblockSize的值每用一次,值就变成原来的两倍。比如开始分配16BYTE的块,下次分配的就是32BYTE的块。

对于ContextDelete来说:

输入:删除块的指针,包含块的Context。

输出:无。

如果块比较大(大的含义和上述类似),则是在Context的blocks链中找到对应的块,从链表中删除,并且调用系统的free操作。如果块比较小,在freelist中找到对应的空闲块链表,并且插入到其中。

对于ContextRealloc来说:

输入:原来块的指针,要分配更大的块的大小,包含块的Context。

输出:返回对应的地址。

对于原来块的大小都是很大的,那么重新分配的块会是更大的。所以从Context的blocks中找到对应块,进行系统的realloc操作。这里需要修改空闲起始指针和块尾指针,以及在链表中要重新进行链接操作,因为,realloc得到的地址可能和原来的地址不同了。而在其他情况下,则按照先ContextAlloc再ContextDelete操作进行,还需要内存内容的拷贝。

 

啊,终于,关于内存管理的部分代码已经分析完成,接下来还有另一部分的内容,待续…


上篇文章中写到针对单个上下文(MemoryContext)而言的内存操作,在上下文中,可以分配内存块,释放以及重分配内存块,创建Context,删除Context,以及重设Context,关于使用Context的好处,我们引用源代码中的src/backend/utils/mmgr/Readme中的一段话,“相比于单纯地使用malloc,free,使用Context可以更容易释放分配出来的内存,而不用逐个释放里面的每个内存块;相对于对每个块做记录(比如说命名)而言,比较快而且比较稳定,不容易产生内存泄露,它可以忽略这些命名,从而不管具体的名字。”

介绍完了单个Context的内容之后,介绍多个Context的管理问题。在创建Context的时候,会传递一个父亲Context的参数。根据源码中的内容,Context之间是按照“森林”数据结构的方式组织的,一个Context可以有多个儿子Context,但是只有一个父亲Context。如此组织Context可以有很多的好处,其中,一个最大好处在于它能处理内存生命期嵌套方式,只要确保短生命期的内存是在相应Context的后代Context中分配的。这个特点在数据库里面是尤其有用的。当删除或者重置一个Context时,它会作用到它以及它所有的后代Context上面上去。

这里简要的给出相应的数据结构

typedef struct MemoryContextData
{
   NodeTag       type;          
   MemoryContextMethods methods;
   MemoryContextData*parent;    
   MemoryContextData *firstchild;
   MemoryContextData *nextchild; 
   char         *name;          
} MemoryContextData, *MemoryContext;

这个数据结构本质上可以看成是抽象超类,methods对应的是个C++类似的虚函数表,特定Context,MemoryContextData的内容是在头部的,设想一个内存块,是特定的内存块,第一部分的数据就是MemoryContextData,后面的内容是特定Context补充的。

typedef struct MemoryContextMethodsData
{
   Pointer    (*alloc) (MemoryContext c, Size size);
   void       (*free_p) (Pointer chunk);
   Pointer    (*realloc) (Pointer chunk, Size newsize);
   void       (*reset) (MemoryContext c);
   void       (*delete) (MemoryContext c);
} MemoryContextMethodsData, *MemoryContextMethods

可以看到,上篇文中介绍的AllocSet实现了MemoryContextMethodsData对应的所有函数。

在实现中,需要注意的是删除和重置操作,它需要递归地对它所有的后代处理。具体的过程,不再作具体分析。感兴趣的您可以读一下相应的这部分代码。它在postgresql-8.4.2\src\backend\utils\mmgr\mcxt.c中



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值