文章目录
- 3.1.2 内存池
- 1. 什么是内存池
- 2. 内存管理
- 1. 定长
- 2. 不定长
- 3. jemalloc
- 4. tcmalloc
3.1.2 内存池
1. 什么是内存池
-
内存池(Memory Pool) 是一种 预先分配 一块大内存,然后按需分配和回收 其中小块内存的技术。它的本质是管理一块连续的大内存区域,避免频繁调用 malloc/free 或 new/delete,提高性能并减少内存碎片。
-
池(线程池、内存池等等)->起一个缓冲作用
-
运行时间长->coredump,内存碎片-> 内存管理组件(内存池)
-
线程池代码都差不多,内存池(指的是虚拟内存管理,就是堆管理)就不一样了
BSS(Block Started by Symbol)段 是程序中的一段内存区域,主要用于存储 未初始化的全局变量 和 静态变量。它的特点是这些变量在程序开始运行时没有被明确地初始化为某个值,而是由操作系统在程序加载时自动填充为零。
2. 内存管理
1. 定长
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MEM_PAGE_SIZE 0x1000 //
typedef struct mempool_s {
int blocksize;// 16, 32, 64, 128
int freecount;//块的个数
char *free_ptr;//开始位置
char *mem;//整块内存
} mempool_t;
// sdk --> varchar(32);
//
// 2^n, page_size 4096, block_size: 16, 32, 64, 128
//内存池创建
int memp_create(mempool_t *m, int block_size) {
if (!m) return -1;
m->blocksize = block_size;
m->freecount = MEM_PAGE_SIZE / block_size;
m->mem = (char *)malloc(MEM_PAGE_SIZE); //分配一个页的内存
if (!m->mem) { //NULL
return -2;
}
memset(m->mem, 0, MEM_PAGE_SIZE); // 都置0
m->free_ptr = m->mem;
int i = 0;
char *ptr = m->mem;
for (i = 0; i < m->freecount; i++) { // 分块,并构建链表
*(char **)ptr = ptr + block_size; // 当前块的指针指向下一个块
//ptr 是 char*,表示一个内存地址。
//(char **)ptr 让 ptr 被解释为指向 char* 的指针(即存储的是 指针)。
//*(char **)ptr = ptr + block_size; 将下一个块的地址存入当前块的起始位置,构建 单链表。
//可以想象它像 链表的 next 指针,但它存储在内存块的开头
ptr = ptr + block_size; // 移动到下一个块
}
*(char **)ptr = NULL; // 最后一块的指针指向 NULL
return 0;
}
void memp_destory(mempool_t *m) {
if (!m) return ;
free(m->mem);
}
void *memp_alloc(mempool_t *m) {
if (!m || m->freecount == 0) return NULL;
void *ptr = m->free_ptr;
m->free_ptr = *(char **)ptr;
m->freecount --;
return ptr;
}
void memp_free(mempool_t *m, void *ptr) {
//头插法,把释放的块放到链表的头部
*(char **)ptr = m->free_ptr;
m->free_ptr = (char *)ptr;
m->freecount ++;
}
// memp_strcpy
// memp_memcpy
int main() {
mempool_t m;
memp_create(&m, 32);
void *p1 = memp_alloc(&m);
printf("memp_alloc : %p\n", p1);
void *p2 = memp_alloc(&m);
printf("memp_alloc : %p\n", p2);
void *p3 = memp_alloc(&m);
printf("memp_alloc : %p\n", p3);
memp_free(&m, p2);
}
- 释放完的块,如何被再次使用
在定长(固定大小)内存池中,释放后的内存块通常通过**空闲链表(Free List)**管理,以便后续分配时可以快速复用 - 4K以上(大块),4K以下(小块)
在内存管理中,4KB 是一个重要的阈值,通常与分页(Page)机制相关
2. 不定长
// 内存块节点(小块内存的管理单元)
typedef struct mp_node_s {
unsigned char *last; // 指向当前块的可用内存的起始地址
unsigned char *end; // 指向当前块的结束地址
struct mp_node_s *next; // 指向下一个内存块的指针
} mp_node_t;
// 大块内存节点(存储超出 max 限制的大块内存)
typedef struct mp_large_s {
struct mp_large_s *next; // 指向下一个大块
void *alloc; // 指向分配的大块内存
} mp_large_t;
// 内存池结构体
typedef struct mp_pool_s {
size_t max; // 小块内存的最大分配大小
struct mp_node_s *head; // 指向链表的头节点(第一个小块内存)
struct mp_large_s *large; // 指向大块内存的链表
} mp_pool_t;
int mp_create(mp_pool_t *pool, size_t size);
void mp_destory(mp_pool_t *pool);
void *mp_alloc(mp_pool_t *pool, size_t size);
void mp_free(mp_pool_t *pool, void *ptr);
// size : 4096
int mp_create(mp_pool_t *pool, size_t size) {
if (!pool || size <= 0) return -1; // 检查参数是否合法
void *mem = malloc(size); // 分配 size 大小的内存
//void*(空指针类型)可以指向任何类型的数据,但它不能直接解引用,需要先转换为具体的指针类型才能解引用。
// 初始化第一个小块节点
struct mp_node_s *node = (struct mp_node_s *)mem;
node->last = (char *)mem + sizeof(struct mp_node_s); // last 指向第一个可用地址
node->end = (char *)mem + size; // 记录块的结束位置
node->next = NULL; // 目前只有一个块
// 初始化内存池
pool->head = node;
pool->max = size; // 记录最大小块内存大小
pool->large = NULL; // 初始时没有大块内存
return 0;
}
void mp_destory(mp_pool_t *pool) {
mp_large_t *l;
// 释放所有大块内存
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
pool->large = NULL;
// 释放所有小块内存
mp_node_t *node = pool->head;
while (node) {
mp_node_t *tmp = node->next;
free(node);
node = tmp;
}
}
// 分配小块内存
void *mp_alloc(mp_pool_t *pool, size_t size) {
if (size > pool->max) {
// 如果请求的大小超过 max,直接分配大块
return mp_alloc_large(pool, size);
}
void *ptr = NULL;
mp_node_t *node = pool->head;
// 遍历小块链表,查找有足够空间的块
do {
if (node->end - node->last > size) {
ptr = node->last;
node->last += size; // 移动 last 指针
return ptr;
}
node = node->next;
} while (node);
// 如果没有可用空间,则创建新的小块
return mp_alloc_block(pool, size);
}
// 分配大块内存
static void *mp_alloc_large(mp_pool_t *pool, size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) return NULL;
mp_large_t *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc == NULL) {
l->alloc = ptr;
return ptr;
}
}
l = mp_alloc(pool, sizeof(mp_large_t));
if (l == NULL) {
free(ptr);
return NULL;
}
l->alloc = ptr;
l->next = pool->large;
pool->large = l;
return ptr;
}
//分配新的小块(扩展内存池)
static void *mp_alloc_block(mp_pool_t *pool, size_t size) {
// 创建新内存块
void *mem = malloc(pool->max);
struct mp_node_s *node = (struct mp_node_s *)mem;
node->last = (char *)mem + sizeof(struct mp_node_s);
node->end = (char *)mem + pool->max;
node->next = NULL;
void *ptr = node->last;
node->last += size; // 更新 last
// 将新块插入到链表末尾
mp_node_t *iter = pool->head;
while (iter->next != NULL) {
iter = iter->next;
}
iter->next = node;
return ptr;
}
// 释放指定内存
void mp_free(mp_pool_t *pool, void *ptr) {
mp_large_t *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc == ptr) {
free(l->alloc);
l->alloc = NULL;
return;
}
}
}
int main() {
mp_pool_t pool;
mp_create(&pool, 4096);
void *p1 = mp_alloc(&pool, 100);
void *p2 = mp_alloc(&pool, 200);
void *p3 = mp_alloc_large(&pool, 5000);
mp_free(&pool, p3);
mp_destory(&pool);
}
graph TD
A[mp_create] -->|初始化内存池| B[mp_node_t]
A --> C[mp_large_t]
B --> D[mp_alloc_block]
B --> E[mp_alloc_large]
D -->|分配内存块| F[mp_alloc]
E -->|分配大块内存| F
F --> G[mp_free]
G --> H[mp_destory]
F --> I[mp_alloc_block]
C --> J[mp_node_t]
subgraph A [创建内存池]
A1[创建内存池]
A2[初始化小块内存]
A3[初始化大块内存]
end
subgraph D [分配内存块]
D1[检查是否有足够空间]
D2[创建新内存块并添加到链表]
end
subgraph F [分配内存]
F1[根据大小决定分配]
F2[调用对应的分配函数]
end
subgraph G [释放内存]
G1[释放小块内存]
G2[释放大块内存]
end
subgraph H [销毁内存池]
H1[释放所有内存块]
end
A1 --> A2
A2 --> A3
D1 --> D2
F1 --> F2
G1 --> G2
H1 --> H
3. jemalloc
- 适用于高并发、多线程应用(如数据库、缓存系统)
- 低碎片率,更适合长期运行的服务
- 支持
madvise()
释放未使用内存,降低 RSS 占用
4. tcmalloc
- 超快的小对象分配,减少
malloc/free
开销 - 适用于 Google 生态,如 gRPC、Go 语言 runtime
- 批量释放机制,但不会主动归还系统内存
特性 | jemalloc | tcmalloc |
---|---|---|
线程缓存 | ✅ 线程私有 tcache,减少锁竞争 | ✅ 线程本地缓存(Thread-local Cache) |
碎片率 | ✅ 更低,适合长期运行的系统 | ❌ 相对较高 |
小对象分配 | ✅ 使用 Bins 机制优化 | ✅ 高效,适用于小对象高频申请 |
大对象管理 | ✅ Extents 细粒度管理 | ✅ mmap 直接分配,减少锁争用 |
释放策略 | ✅ madvise() 归还内存 | ❌ 批量回收,但不主动归还系统 |
适用场景 | ✅ Redis、MySQL、MongoDB | ✅ Google 生态、Go runtime、gRPC |