必须理解ngx_palloc不支持realloc、不能跨请求传指针、4KB是临界点三件事,否则调优反致崩溃或泄漏;其碎片主因是绕过池使用libc堆、误用已销毁池、未初始化指针等。

直接结论:不用 ngx_palloc 就没法规避碎片,但只用它还不够——必须理解它不支持 realloc、不能跨请求传指针、4KB 是临界点这三件事,否则调优反而引发崩溃或内存泄漏。
为什么 ngx_palloc 分配后还会有内存碎片?
常见错误现象是压测中 RSS 持续上涨、perf 显示 malloc 调用占比突增,甚至出现 segmentation fault。这不是 ngx_palloc 失效,而是你绕过了它:
- 第三方模块(如
lua-nginx-module)内部调用了strdup或asprintf,这些走的是 libc 堆,和r->pool完全隔离 - 在定时器回调(
ngx_add_timer)里误用了r->pool,而此时请求早已销毁,池内存已被ngx_destroy_pool归还 - 结构体字段里存了
char *但没用ngx_pcalloc初始化,导致指向随机地址,后续strcpy写越界
ngx_palloc 和 ngx_pcalloc 的关键差异在哪?
两者都从 pool 分配,但行为边界完全不同:
-
ngx_palloc只移动last指针,不做清零,适合已知会立即赋值的 buffer(如解析后的 URL 字符串) -
ngx_pcalloc在ngx_palloc后额外调用memset(0),适合结构体分配(如ngx_http_upstream_conf_t),避免未定义行为 - 分配接近 4096 字节(默认
pool->max)时,ngx_palloc会 fallback 到malloc并挂入large链表——这本身不碎片,但若大量4095字节请求并发,会导致large链表暴涨,销毁时需遍历释放,延迟上升
如何安全复用 chain 节点和预分配 buffer?
HTTP filter 阶段频繁新建 ngx_chain_t 是碎片高发区,不是因为分配慢,而是因为节点生命周期混乱:
- 别每次用
ngx_alloc_chain_link(r->pool),改用ngx_chain_get_free_buf(r->pool)拿空闲节点;它从r->pool->chain复用,不触发新分配 - 固定头字段(如
"HTTP/1.1 200 OK\r\n")用静态全局 buffer,或在模块postconfiguration阶段用cf->cycle->pool预分配,避免 per-request 开销 - 上传 body 缓冲区不要长期 hold 在
r->pool,应调用ngx_create_temp_buf并显式管理其cleanup回调,防止撑爆主池
调试时怎么确认内存真在池里?
开启 NGX_DEBUG_POOL 编译宏后,日志里会出现类似 pool: 000055AABBCCDD00, size: 4096, used: 2840 的行,但更关键的是检查分配路径:
- 所有模块代码中禁止出现
malloc、calloc、strdup字样,grep 全局确认 - 用
addr2line -e objs/nginx -f -C查看崩溃地址是否落在ngx_palloc.c的ngx_palloc_block函数内——若在malloc符号里,说明有隐式堆分配 - 检查
ngx_pool_t的current字段是否被意外修改(如多线程写共享池),Nginx 的 pool 本身无锁,跨线程使用必崩
最易被忽略的点是:pool 不是“可重用资源”,而是“请求快照”。哪怕你把 r->pool 存进一个全局 map,几秒后取出来用,也大概率访问已释放内存——因为 ngx_destroy_pool 不仅 free large 链表,还会把整个 pool block(通常是 malloc 来的)直接 free() 掉。没有“池回收”这回事,只有“整批生灭”。


















