必须显式调用imagedestroy()释放GD图像资源,因其底层由C malloc分配,PHP垃圾回收无法自动回收;不销毁会导致RSS内存持续增长,即使脚本结束也不释放。

验证码生成后内存不回落,不是因为字母本身占内存,而是图像资源、临时字符串缓存或 GD 句柄没释放——imagecreate() 分配的 C 层内存不会被 PHP GC 自动回收。
GD 图像资源必须显式调用 imagedestroy()
PHP 验证码普遍用 GD 库画图,imagecreate() 或 imagecreatetruecolor() 返回的是资源类型(resource),底层由 C malloc 分配,Zend 内存管理器完全不感知。不调用 imagedestroy($img),这块内存就一直挂在进程 RSS 里,脚本结束也不还给系统。
- 常见错误写法:
return imagepng($img);后直接退出,$img变量虽失效,但资源未销毁 - 正确顺序:先输出图像,再销毁,且确保所有分支都覆盖 —— 包括异常提前 return 的路径
- 示例片段:
$img = imagecreatetruecolor(120, 40); // ... 绘制文字、干扰线 ob_start(); imagepng($img); $output = ob_get_clean(); imagedestroy($img); // 必须在这行之后才能安全释放 echo $output;
随机字符串缓存别塞进 static 或全局数组
有些验证码逻辑会把生成的字母存到 self::$cache 或 $GLOBALS['captcha_code'] 里供后续校验,若不主动清理,每次请求都追加,静态变量生命周期贯穿整个 FPM worker 进程,内存只增不减。
- 避免:
static $codes = []; $codes[$key] = $str;—— 没上限、不清理 - 推荐:
apcu_store("captcha_{$key}", $str, 300)+ 显式过期,或用$_SESSION['captcha'](随 session 销毁自动丢) - 如果非用 static,务必在验证通过/失败后调用
unset(self::$cache[$key]),不能只靠“下次覆盖”
memory_get_usage() 看不到 GD 内存,得看 ps aux RSS
memory_get_usage() 只统计 Zend MM 管理的 PHP 层内存(比如字符串、数组),对 GD 分配的图像像素缓冲区、freetype 字体渲染内存完全无感。你看到 memory_get_usage() 很低但进程 RSS 持续上涨,基本就是 GD 资源泄漏。
立即学习“PHP免费学习笔记(深入)”;
- 验证方法:CLI 下跑验证码脚本 100 次,用
ps aux --sort=-%mem | head -5观察同一进程 PID 的 RSS 是否递增 - 调试技巧:Linux 下启用
ZEND_MM_DEBUG=1运行(USE_ZEND_ALLOC=0会禁用 Zend MM,让 malloc/free 直接暴露),配合valgrind --leak-check=full php script.php定位未配对的 emalloc/efree - 注意:
gc_collect_cycles()对 GD 资源无效 —— 它只处理 PHP 对象循环引用,不碰 C 扩展资源
真正卡住内存的从来不是那几个字母,而是画字母用的画布、字体缓存、临时字符串拼接时的冗余副本;GD 不销毁、缓存不清理、错误路径漏掉 imagedestroy(),哪怕只漏一次,常驻进程里就多占几 MB RSS,积少成多直接拖垮 FPM worker。



















