PostgreSQL(以下简称PG)遇到OOM,相信是大家非常头疼的问题。当PG发生OOM时,会造成:
- 所有session processes被系统kill,引起应用连接不可用;
- postmaster被被系统kill,数据库需要重启,引起数据库系统不可用。
这都涉及到了PG的内存管理系统。PG的内存管理分为共享内存管理以及本地内存管理,本文主要对本地内存管理进行介绍。
PostgreSQL的MemoryContext机制实现,本质上是对malloc,free,realloc的封装。但是glibc的malloc为了多线程架构的实现,加入了一些锁来保证并发的一致性,而PostgreSQL本身是多进程单线程的架构,所以完全使用glibc的内存接口,性能是有损耗的。
其次在PostgreSQL这样工程较为庞大,函数调用较为复杂的数据库管理系统内,对于内存的管理,如果使用malloc和free进行内存的申请和释放,是很难做到尽善尽美。在7.0之前的版本中,PG以指针传递来处理大量查询,并且在查询结束后回收内存,因此存在着内存泄漏的风险。尤其是处理TOAST数据时,需要使用大量的内存,使得这个问题尤其明显。
因此,在7.1版本之后,PG实现了新的内存管理机制--MemoryContext。整个MemoryContext是以树形结构来组织管理的,这能够很容易的对MemoryContext进行管理。
本文将会以下面几个部分进行阐述:
- MemoryContext的基础结构
- MemoryContext的操作函数
术语解释:
- MemoryContext,PostgreSQL实现的内存上下文管理机制;
- block,内存块
- chunk,内存片
MemoryContext基础结构:MemoryContextData,AllocSetContextData,AllocBlockData,AllocChunkData,AllocSetFreeList
在搞清楚MemoryContext之前,我们首先要清晰地认识MemoryContextData,AllocSetContextData,AllocBlockData,AllocChunkData。
MemoryContext(这里指的是指针名称),AllocSetContext,AllocBlock,AllocChunk是上面四者的指针类型,下文阐述时会以带有Data的名称表示这四种结构。
首先看下这四者的整体关系结构图:
AllocSetContext is our standard implementation of MemoryContext.[1]
AllocSetContextData是MemoryContext的标准实现。类似于简单的操作系统内存管理,要组织已使用内存和已空闲内存的管理。在进程中对MemoryContext的操作实际是对AllocSetContextData的操作。
MemoryContextData只是用来表示进程操作期间的不同MemoryContext之间的关系(父子,前后),不关心内存本身的分配和释放,是一个抽象类型,可以有多种实现。同时MemoryContextData的methods定义了一种特定实现方式。而作为MemoryContext的实际实现,每个AllocSetContextData的起始位置必须是MemoryContextData,这类似于面向对象中的继承概念。MemoryContextData是父类,AllocSetContextData是子类,继承了父类的变量,并增加了一些成员变量以及操作接口。
我们可以使用MemoryContextData将整个MemoryContext抽象表示一个树形结构:
AllocBlockData是进程用来分配、释放的内存单元,通过malloc申请。AllocChunkData是用户存放数据的内存单元,通过palloc申请,其中已经使用的AllocChunkData的aset将会指向所属的AllocSetContextData;如果是空闲未使用的将会指向freelist中的下一个成员变量(没有则为空)。一个AllocBlockData具有一个或多个AllocChunkData。[2]
四者的数据结构
MemoryContext的数据结构为:
typedef struct MemoryContextData
{
NodeTagtype;/* identifies exact kind of context */
/* these two fields are placed here to minimize alignment wastage: */
boolisReset;/* T = no space alloced since last reset */
boolallowInCritSection; /* allow palloc in critical section */
const MemoryContextMethods *methods;/* virtual function table */
MemoryContext parent;/* NULL if no parent (toplevel context) */
MemoryContext firstchild;/* head of linked list of children */
MemoryContext prevchild;/* previous child of same parent */
MemoryContext nextchild;/* next child of same parent */
const char *name;/* context name (just for debugging) */
const char *ident;/* context ID if any (just for debugging) */
MemoryContextCallback *reset_cbs;/* list of reset/delete callbacks */
} MemoryContextData;
- type 标示内存上下文类型,默认只有一种类型,T_AllocSetContext
- isReset:True表示上次重置后没有内存申请动作发生,False表示已经发生内存申请
- allowInCritSection: 是否允许在临界区中分配内存。通常来说是不允许进行这样分配的,分配失败会导致PANIC,但这可以用于调试代码,方便开发调试,这些代码不建议进入实际生成环境中
- parent,prevchild,nextchild组成内存上下文的树形结构,方便在重置或删除内存上下文时查找子节点,分别表示父节点,同一个父节点下的prev节点,同一个父节点下的next节点
- name: MemoryContext的名称,不同的MemoryContext具有不同的MemoryContext。方便调试
- ident: MemoryContext的ID,方便调试
- reset_cbs: Postgres 9.5中引入的一项功能允许将内存上下文用于管理更多资源,而不仅仅是普通的palloc分配的内存。 这是通过为内存上下文注册“重置回调函数”来完成的。 在下一次重置或删除上下文之前,将调用一次此类函数。 它可以用来放弃在某种意义上与上下文中分配的对象相关联的资源。
- methods: 记录了内存上下文使用的函数指针,MemoryContextMethods的数据结构:
typedef struct MemoryContextMethods
{
void *(*alloc) (MemoryContext context, Size size);
/* call this free_p in case someone #define's free() */
void(*free_p) (MemoryContext context, void *pointer);
void *(*realloc) (MemoryContext context, void *pointer, Size size);
void(*reset) (MemoryContext context);
void(*delete_context) (MemoryContext context);
Size(*get_chunk_space) (MemoryContext context, void *pointer);
bool(*is_empty) (MemoryContext context);
void(*stats) (MemoryContext context,
MemoryStatsPrintFunc printfunc, void *passthru,
MemoryContextCounters *totals);
#ifdef MEMORY_CONTEXT_CHECKING
void(*check) (MemoryContext context);
#endif
} MemoryContextMethods;
在本文第二章基本操作会详细介绍各个函数操作。
AllocSetContextData的数据结构:
typedef struct AllocSetContext
{
MemoryContextData header;/* Standard memory-context fields */
/* Info about storage allocated in this context: */
AllocBlockblocks;/* head of list of blocks in this set */
AllocChunkfreelist[ALLOCSET_NUM_FREELISTS];/* free chunk lists */
/* Allocation parameters for this context: */
SizeinitBlockSize;/* initial block size */
SizemaxBlockSize;/* maximum block size */
SizenextBlockSize;/* next block size to allocate */
SizeallocChunkLimit;/* effective chunk size limit */
AllocBlockkeeper;/* keep this block over resets */
/* freelist this context could be put in, or -1 if not a candidate: */
intfreeListIndex;/* index in context_freelists[], or -1 */
} AllocSetContext;
- header: 标准的内存上下文区域,类型为MemoryContextData
- blocks: 内存块链表,记录内存上下文向操作系统申请的连续大块内存
- initBlockSize,maxBlockSize,nextBlockSize: 内存上下文创建时指定的初始block大小,最大block大小和下一次分配的block大小。一般来说,下一次申请的block会是上一次的2被,但不会超过maxBlockSize
- allocChunkLimit: 内存片的阈值,申请的内存超过此阀值直接分配新的block
- keeper: 为防止一些需要频繁重置的小的内存上下文重复的进行malloc,重置时保留第一次申请的内存块
- freeListIndex,在context_freelists[3]顺序,0表示默认freeList,1表示小内存freeList,-1表示不需要进入freeList(例如超过allocChunkLimit的block)
- freelist: 组织该内存上下文里所有内存块中已释放的内存片的链表结构,这是一个具有ALLOCSET_NUM_FREELISTS成员的AllocChunkData数组,成员分别表示不同大小的AllocChunkData,每个相同大小的AllocChunkData由aset指针进行链接。每个一个成员是上一个的2倍,因此当前freelist可以存储的AllocChunkData大小有8bytes,16bytes,32bytes,64bytes,128bytes,256bytes,512bytes,1024bytes,2048bytes,4096bytes,8192bytes,freelist可以表示为:
AllocBlockData的数据结构:
typedef struct AllocBlockData
{
AllocSetaset;/* aset that owns this block */
AllocBlockprev;/* prev block in aset's blocks list, if any */
AllocBlocknext;/* next block in aset's blocks list, if any */
char *freeptr;/* start of free space in this block */
char *endptr;/* end of space in this block */
}AllocBlockData;
- aset: 指向AllocBlockData所属的AllocSetContext
- prev: 在AllocSetContext中的blocks prev block
- next: 在AllocSetContext中的blocks next block
- freeptr: 可用空闲区域的起始地址
- endptr: 可用空闲区域的结束地址
AllocChunkData的数据结构:
typedef struct AllocChunkData
{
/* size is always the size of the usable space in the chunk */
Sizesize;
#ifdef MEMORY_CONTEXT_CHECKING
/* when debugging memory usage, also store actual requested size */
/* this is zero in a free chunk */
Sizerequested_size;
#define ALLOCCHUNK_RAWSIZE (SIZEOF_SIZE_T * 2 + SIZEOF_VOID_P)
#else
#define ALLOCCHUNK_RAWSIZE (SIZEOF_SIZE_T + SIZEOF_VOID_P)
#endif/* MEMORY_CONTEXT_CHECKING */
/* ensure proper alignment by adding padding if needed */
#if (ALLOCCHUNK_RAWSIZE % MAXIMUM_ALIGNOF) != 0
charpadding[MAXIMUM_ALIGNOF - ALLOCCHUNK_RAWSIZE % MAXIMUM_ALIGNOF];
#endif
/* aset is the owning aset if allocated, or the freelist link if free */
void *aset;
/* there must not be any padding to reach a MAXALIGN boundary here! */
}AllocChunkData;
- size: 当前AllocChunkData的长度
- requested_size: 在debugging模式下会记录实际请求的长度
- padding:对齐时使用
- aset:当此AllocChunkData正在使用时,指向所属的AllocSetContext;未使用则指向下一个相同大小的空闲AllocChunkData(没有则为空)
还有一个比较重要的数据结构是AllocSetFreeList,和上面AllocSetContextData的freelist不同,这里指的是空闲的AllocSetContextData,而非AllocChunkData。当发生Delete操作时,会将删除后的AllocSetContextData放入AllocSetFreeList。
AllocSetFreeList在每个进程使用了数组进行存储:
static AllocSetFreeList context_freelists[2] =
{
{
0, NULL
},
{
0, NULL
}
};
- 第一个成员用来存放普通大小的AllocSetContextData
- 第二个成员用来存放较小尺寸的AllocSetContextData
AllocSetFreeList的数据结构:
#define MAX_FREE_CONTEXTS 100/* arbitrary limit on freelist length */
typedef struct AllocSetFreeList
{
intnum_free;/* current list length */
AllocSetContext *first_free;/* list header */
} AllocSetFreeList;
- num_free,空闲AllocSetContextData的数量,超过MAX_FREE_CONTEXTS,即100后将会对此freelist进行清理,避免内存过于松散
- first_free指向第一个AllocSetContextData,相同类别的AllocSetContextData使用nextchild进行指向
AllocSetContextData的函数接口
PostgreSQL的MemoryContext应该是借鉴了很多Linux对于内存的管理。同时提供了palloc,repalloc,pfree等函数调用接口。由于对PG对内存的管理其实是对AllocSetContextData的管理,并且由MemoryContextData的methods定义了一种特定实现方式。那么首先看下这些函数实现:
static const MemoryContextMethods AllocSetMethods = {
AllocSetAlloc,
AllocSetFree,
AllocSetRealloc,
AllocSetReset,
AllocSetDelete,
AllocSetGetChunkSpace,
AllocSetIsEmpty,
AllocSetStats
#ifdef MEMORY_CONTEXT_CHECKING
,AllocSetCheck
#endif
};
在阐述以上函数时我们首先单独介绍一个函数AllocSetContextCreateExtended
Create a new AllocSet context.
先看下一个AllocSetContextData的初始状态:
AllocSetContextCreateExtended:
/* 查看是否有空闲的freelist */
if (freelist->first_free != NULL)
{
/* 有空闲的AllocSetContextData,则从freelist移除 */
set = freelist->first_free;
/* 将first_free指向原有的AllocSetContextData下一个节点 */
freelist->first_free = (AllocSet) set->header.nextchild;
freelist->num_free--;/* 空闲AllocSetContextData数量减一 */
/* 设置maxBlockSize,同一个freelist具有相同的initBlockSize */
set->maxBlockSize = maxBlockSize;
/* 使用MemoryContextCreate将此AllocSetContextData加入到MemoryContext中 */
MemoryContextCreate((MemoryContext) set,
T_AllocSetContext,
&AllocSetMethods,
parent,
name);
return (MemoryContext) set;
}
/* 没有freelist,则新建AllocSetContextData */
/* 这里使用malloc来申请、分配内存 */
set = (AllocSet) malloc(firstBlockSize);
/* 找到block的位置 */
block = (AllocBlock) (((char *) set) + MAXALIGN(sizeof(AllocSetContext)));
/* 当前block指向所属的AllocSetContextData */
block->aset = set;
/* 初始化的AllocBlockData,不设置AllocChunkData,因此freeptr指向AllocBlockData之后的内存地址 */
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
/* endptr指向block最后地址 */
block->endptr = ((char *) set) + firstBlockSize;
block->prev = NULL;
block->next = NULL;
set->blocks = block;
set->keeper = block;
set->initBlockSize = initBlockSize;
set->maxBlockSize = maxBlockSize;
set->nextBlockSize = initBlockSize;
set->freeListIndex = freeListIndex;
/* 使用MemoryContextCreate将此AllocSetContextData加入到MemoryContext中 */
MemoryContextCreate((MemoryContext) set,
T_AllocSetContext,
&AllocSetMethods,
parent,
name);
return (MemoryContext) set;
AllocSetAlloc
Returns pointer to allocated memory of given size or NULL if request could not be completed; memory is added to the set.
实施内存分配的函数,内部调用了malloc函数。具体分配方式可以分为两种:
- 申请的内存size大于allocChunkLimit
- 申请的内存size小于等于allocChunkLimit
size > allocChunkLimit
/* 当申请的内存空间size大于内存片阀值,则直接申请新的AllocBlockData */
if (size > set->allocChunkLimit)
{
/* 对齐字节 */
chunk_size = MAXALIGN(size);
/* 预留AllocBlockData,AllocChunkData空间 */
blksize = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
block = (AllocBlock) malloc(blksize);
/* malloc申请失败直接返回null */
if (block == NULL)
return NULL;
/* 当前block指向所属的AllocSetContextData */
block->aset = set;
/* 由于是为内存申请单独申请的block,所以不需要有多个chunk,所以freeptr和endptr都指向最后的地址,表示没有空闲空间 */
block->freeptr = block->endptr = ((char *) block) + blksize;
/* 建立内存片chunk */
chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
/* 当前chunk指向所属的AllocSetContextData */
chunk->aset = set;
chunk->size = chunk_size;
/* 将当前block加入到blocks中 */
if (set->blocks != NULL)
{
/* 如果存在blocks,则将此block加入到blocks的第二个位置 */
block->prev = set->blocks;
block->next = set->blocks->next;
if (block->next)
block->next->prev = block;
set->blocks->next = block;
}
else
{
/* 如果不存在blocks,则将blocks直接指向此block */
block->prev = NULL;
block->next = NULL;
set->blocks = block;
}
return AllocChunkGetPointer(chunk);
}
size <= allocChunkLimit
/* 根据size计算log2(size)+1 */
fidx = AllocSetFreeIndex(size);
/* 选取空闲的chunk */
chunk = set->freelist[fidx];
if (chunk != NULL)
{
/* 如果不为null,将此chunk从freelist中移除,并返回 */
set->freelist[fidx] = (AllocChunk) chunk->aset;
chunk->aset = (void *) set;
return AllocChunkGetPointer(chunk);
}
/* 如果不存在空闲的chunk,就需要申请新的chunk,需要计算其size */
chunk_size = (1 << ALLOC_MINBITS) << fidx;
/* 查看是否有合适的block可以申请chunk */
if ((block = set->blocks) != NULL)
{
/* 计算是否有足够的空闲空间 */
Sizeavailspace = block->endptr - block->freeptr;
/*
* 如果空闲空间不足,则将已有的空闲空间划分为chunk。
* 这么做的原因是blocks的第一个block作为活跃内存块,
* 并且当前设计只对block做一次检查,避免不必要的消耗。
*/
if (availspace < (chunk_size + ALLOC_CHUNKHDRSZ))
{
/* 对空闲空间进行申请chunk,空闲chuck最小为(1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ */
while (availspace >= ((1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ))
{
Sizeavailchunk = availspace - ALLOC_CHUNKHDRSZ;
inta_fidx = AllocSetFreeIndex(availchunk);
/* 确定合适的chunk size,因为block所有的内容都是经过对齐的,因此能够找到合适的大小 */
if (availchunk != ((Size) 1 << (a_fidx + ALLOC_MINBITS)))
{
a_fidx--;
Assert(a_fidx >= 0);
availchunk = ((Size) 1 << (a_fidx + ALLOC_MINBITS));
}
/* 找到后直接使用此chunk */
chunk = (AllocChunk) (block->freeptr);
block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ);
availspace -= (availchunk + ALLOC_CHUNKHDRSZ);
chunk->size = availchunk;
/* 将chunk加入到freelist内 */
chunk->aset = (void *) set->freelist[a_fidx];
set->freelist[a_fidx] = chunk;
}
/* 设置block为null,设置创建新block的标记 */
block = NULL;
}
}
/* 如果block是null,创建新的block */
if (block == NULL)
{
Sizerequired_size;
blksize = set->nextBlockSize;
required_size = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
/* 如果下一次应申请的block的大小小于当前申请的内存大小,则左移一位,知道大于等于申请的大小 */
while (blksize < required_size)
blksize <<= 1;
block = (AllocBlock) malloc(blksize);
/*
* 如果malloc失败,则继续申请,失败是因为可能blksize远远大于required_size,
* 也超出当前剩余内存。当申请的内存小于1MB或blksize < required_size则放弃申请。
*/
while (block == NULL && blksize > 1024 * 1024)
{
blksize >>= 1;
if (blksize < required_size)
break;
block = (AllocBlock) malloc(blksize);
}
if (block == NULL)
return NULL;
block->aset = set;
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
block->endptr = ((char *) block) + blksize;
block->prev = NULL;
block->next = set->blocks;
if (block->next)
block->next->prev = block;
set->blocks = block;
}
chunk = (AllocChunk) (block->freeptr);
block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ);
chunk->aset = (void *) set;
chunk->size = chunk_size;
return AllocChunkGetPointer(chunk);
AllocSetFree
static void
AllocSetFree(MemoryContext context, void *pointer)
{
AllocSetset = (AllocSet) context;
AllocChunkchunk = AllocPointerGetChunk(pointer);
if (chunk->size > set->allocChunkLimit)
{
/* chunk大小超过chunk阀值,则直接释放此chunk所属的block */
AllocBlockblock = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ);
/* 从所属的blocks中移除 */
if (block->prev)
block->prev->next = block->next;
else
set->blocks = block->next;
if (block->next)
block->next->prev = block->prev;
free(block);
}
else
{
/* 一般大小,则加入到freelist中,并 */
intfidx = AllocSetFreeIndex(chunk->size);
/* chunk的aset指向freelist */
chunk->aset = (void *) set->freelist[fidx];
set->freelist[fidx] = chunk;
}
}
AllocSetRealloc
static void *
AllocSetRealloc(MemoryContext context, void *pointer, Size size)
{
AllocSetset = (AllocSet) context;
AllocChunkchunk = AllocPointerGetChunk(pointer);
Sizeoldsize;
oldsize = chunk->size;
/* 如果此chunk的size大于内存片的阀值,则表示这是单独的block,直接对block进行处理 */
if (oldsize > set->allocChunkLimit)
{
AllocBlockblock = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ);
Sizechksize;
Sizeblksize;
chksize = Max(size, set->allocChunkLimit + 1);
chksize = MAXALIGN(chksize);
/* 使用realloc对此block进行处理 */
blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
block = (AllocBlock) realloc(block, blksize);
if (block == NULL)
{
return NULL;
}
block->freeptr = block->endptr = ((char *) block) + blksize;
/* 在realloc后的block申请chunk */
chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
pointer = AllocChunkGetPointer(chunk);
if (block->prev)
block->prev->next = block;
else
set->blocks = block;
if (block->next)
block->next->prev = block;
chunk->size = chksize;
return pointer;
}
else if (oldsize >= size)
{
/* 如果realloc的大小,小于chunk大小,直接返回 */
return pointer;
}
else
{
/* 如果realloc的大小,大于chunk大小 */
AllocPointer newPointer;
/* 申请新的chunk */
newPointer = AllocSetAlloc((MemoryContext) set, size);
/* 将原有内容复制到新的chunk内 */
memcpy(newPointer, pointer, oldsize);
/* 释放原来的chunk */
AllocSetFree((MemoryContext) set, pointer);
return newPointer;
}
}
AllocSetReset
/* 设置blocks指向keeper,reset不对keeper进行释放,只进行重置 */
set->blocks = set->keeper;
while (block != NULL)
{
AllocBlocknext = block->next;
if (block == set->keeper)
{
char *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ;
block->freeptr = datastart;
block->prev = NULL;
block->next = NULL;
}
else
{
/* 非keeper,直接释放 */
free(block);
}
block = next;
}
AllocSetDelete
static void
AllocSetDelete(MemoryContext context)
{
AllocSetset = (AllocSet) context;
AllocBlockblock = set->blocks;
/* 查看此AllocSetContextData是否是freelist的候选者 */
if (set->freeListIndex >= 0)
{
AllocSetFreeList *freelist = &context_freelists[set->freeListIndex];
/* 当前MemoryContext没有被重置,则重置 */
if (!context->isReset)
MemoryContextResetOnly(context);
/* 如果freelist的空闲AllocSetContextData数量超过MAX_FREE_CONTEXTS后,删除所有的空闲AllocSetContextData */
if (freelist->num_free >= MAX_FREE_CONTEXTS)
{
while (freelist->first_free != NULL)
{
AllocSetContext *oldset = freelist->first_free;
freelist->first_free = (AllocSetContext *) oldset->header.nextchild;
freelist->num_free--;
free(oldset);
}
}
/* 将此AllocSetContextData加入到freelist中 */
set->header.nextchild = (MemoryContext) freelist->first_free;
freelist->first_free = set;
freelist->num_free++;
return;
}
/* 如果不是freelist的候选MemoryContext,则直接释放 */
while (block != NULL)
{
AllocBlocknext = block->next;
if (block != set->keeper)
free(block);
block = next;
}
free(set);
}
AllocSetGetChunkSpace
result = chunk->size + ALLOC_CHUNKHDRSZ;
return result;
AllocSetIsEmpty
返回此AllocSetContextData是否为空,这里的空指的是新的或者reset,delete(delete)[4]后的AllocSetContextData。
if (context->isReset)
return true;
return false;
AllocSetStats
遍历当前AllocSetContextData的blocks,统计内存申请总量,block数,未使用的chunk数,以及使用的内存大小。
/* 遍历blocks,确定使用了多少block */
for (block = set->blocks; block != NULL; block = block->next)
{
nblocks++;
totalspace += block->endptr - ((char *) block);
freespace += block->endptr - block->freeptr;
}
/* 查找空闲chunk链表,计算空闲chunk数量,以及空闲空间大小 */
for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++)
{
AllocChunkchunk;
for (chunk = set->freelist[fidx]; chunk != NULL;
chunk = (AllocChunk) chunk->aset)
{
freechunks++;
freespace += chunk->size + ALLOC_CHUNKHDRSZ;
}
}
MemoryContext的操作函数
对于MemoryContextData的操作本质上就是在调用AllocSetMethods,但是比之要多了一些更高层次的修改。PostgreSQL进程实际调用的内存管理函数就是这里的函数操作接口。
MemoryContextCreate
if (parent)
{
/*
* 如果父节点不为空,将当前MemoryContext的nextchild指向父节点的第一个子节点
* 则将新的MemoryContext作为父节点的第一个子节点
*/
node->nextchild = parent->firstchild;
if (parent->firstchild != NULL)
parent->firstchild->prevchild = node;
parent->firstchild = node;
/* inherit allowInCritSection flag from parent */
node->allowInCritSection = parent->allowInCritSection;
}
else
{
/* 没有父节点,则作为初始节点 */
node->nextchild = NULL;
node->allowInCritSection = false;
}
MemoryContextDelete
/* 如果存在子节点,则删除所有子节点 */
if (context->firstchild != NULL)
MemoryContextDeleteChildren(context);
/* 回调函数 */
MemoryContextCallResetCallbacks(context);
/* 设置父节点为空 */
MemoryContextSetParent(context, NULL);
context->ident = NULL;
/* 删除此MemoryContext */
context->methods->delete_context(context);
MemoryContextDeleteChildren
/* 如果存在子节点,则删除所有子节点 */
while (context->firstchild != NULL)
MemoryContextDelete(context->firstchild);
MemoryContextReset
/* 如果存在子节点,则直接删除所有子节点 */
if (context->firstchild != NULL)
MemoryContextDeleteChildren(context);
/* 如果没有reset过,或者reset后未再申请内存,则对当前MemoryContext进行reset */
if (!context->isReset)
MemoryContextResetOnly(context);
MemoryContextResetOnly
if (!context->isReset)
{
/* 回掉函数 */
MemoryContextCallResetCallbacks(context);
/* 利用methods的reset函数处理MemoryContext */
context->methods->reset(context);
context->isReset = true;
}
MemoryContextResetChildren
/* 递归遍历所有的子节点,进行reset */
for (child = context->firstchild; child != NULL; child = child->nextchild)
{
MemoryContextResetChildren(child);
MemoryContextResetOnly(child);
}
MemoryContextAlloc,MemoryContextAllocZero,MemoryContextAllocZeroAligned,MemoryContextAllocExtended,palloc,palloc0,palloc_extended[5]
/* 申请内存,设置isReset为false */
context->isReset = false;
/* 使用methods的alloc函数申请、分配内存,失败则报错 */
ret = context->methods->alloc(context, size);
if (unlikely(ret == NULL))
{
MemoryContextStats(TopMemoryContext);
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory"),
errdetail("Failed on request of size %zu in memory context \"%s\".",
size, context->name)));
}
return ret;
MemoryContextInit
/* 申请TopMemoryContext和ErrorContext */
TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL,
"TopMemoryContext",
ALLOCSET_DEFAULT_SIZES);
ErrorContext = AllocSetContextCreate(TopMemoryContext,
"ErrorContext",
8 * 1024,
8 * 1024,
8 * 1024);
/* 保证ErrorContext能够一直返回信息 */
MemoryContextAllowInCriticalSection(ErrorContext, true);
pfree
/* 使用methods的free函数释放内存 */
context->methods->free_p(context, pointer);
repalloc
/* 使用methods的realloc函数realloc内存,失败则报错 */
ret = context->methods->realloc(context, pointer, size);
if (unlikely(ret == NULL))
{
MemoryContextStats(TopMemoryContext);
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory"),
errdetail("Failed on request of size %zu in memory context \"%s\".",
size, context->name)));
}
return ret;
总结
相比较使用glibc的malloc等接口,PostgreSQL实现的MemoryContext:
- 使用特定的内存管理方式,以及特定的内存管理函数,减少了malloc和free的使用,降低了开发复杂度,并减少了内存泄漏的风险;
- 由于malloc是会针对多线程进行一定设计,所以加了一定的锁,当内存进行频繁申请和释放时,会有一定的性能损耗,PG释放MemoryContext首先会将其加入到freelist中,减少malloc的频率,提高内存申请的效率。




