暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

图解PostgreSQL--MemoryContext

原创 内核开发者 2025-04-27
255

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的频率,提高内存申请的效率。
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论