真泄漏是压测百万次后rss持续线性上涨且gc不回落,假性溢出则由xdebug、var_dump残留或单次大请求导致,压测后rss可自然回落;须用ps查rss而非memory_get_usage(),并结合gc_collect_cycles()验证。

怎么确认是真泄漏,不是xdebug或var_dump拖累
看到Allowed memory size of XXX bytes exhausted先别改memory_limit——90%的情况是假性溢出。真泄漏的表现是:压测百万次后RSS(常驻集大小)持续线性上涨且不回落;假性溢出则是单次大请求拉高内存后能自然回落。
- 关掉xdebug:
php -d zend_extension= -f start.php status,再压测,错误消失就说明是它在吃内存 - 检查
app/Middleware/和app/Listener/里有没有残留的var_dump()、print_r()或file_get_contents($big_file) - 用
ps -o rss= -p $(pgrep -f "start.php")查真实RSS,比memory_get_peak_usage(true)更准——后者只反映PHP堆,RSS才是OS扣住的物理内存 - 在入口中间件加一行:
echo "RSS: " . (int)shell_exec("ps -o rss= -p " . getmypid()) . "\n";,连续压测30秒看趋势
static数组无限追加是最常见的泄漏源头
Webman worker进程不死,static $cache = []就永远活着。每次请求[]=塞一条,几万次后就是几百MB黑洞——GC根本收不走,因为强引用链没断。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- 全局搜索
static.*\$[a-zA-Z_]、protected static \$cache、self::\$log,重点盯app/Middleware/和app/Listener/ - 错误写法:
self::$data[] = ['uri' => $request->uri(), 'body' => $request->raw()];——$request->raw()可能带几MB原始数据 - 正确做法:加容量上限+循环队列,例如
if (count(self::$cache) > 1000) { array_shift(self::$cache); },再self::$cache[] = $item; - 更稳妥方案:删掉裸
static数组,换Webman\Support\Cache::remember('key', $ttl, fn() => $value),或直接上Redis,TTL兜底
闭包use($this)或use($request)会偷偷锁死整个服务容器
看起来只是读个字段,但use ($this)会让整个实例(含DB连接、日志处理器、服务容器)无法被GC回收。这类泄漏特征是memory_get_usage()回落但RSS卡住不动。
- 排查所有
Event::on()、Timer::tick()、WebSocket::onMessage()里的匿名函数 - 把
use ($request, $response)改成use ($request->id),后续用ID查库,避免绑定整块对象 - PHP 8.0+优先用
WeakMap存上下文:private static WeakMap $connections;,对象销毁后引用自动解除 - 在
onClose或定时器结束回调里,显式调用gc_collect_cycles()并记录RSS变化,验证是否真释放
memory_limit到底该在哪设才生效
.htaccess完全无效,Webman跑在CLI守护模式,配置必须匹配加载路径。设错位置等于没设。
- 首选:在
start.php第一行加ini_set('memory_limit', '512M');(注意:必须早于自动加载和任何业务逻辑) - PHP-FPM部署时,改对应pool的
www.conf,加php_admin_value[memory_limit] = 384M - CLI启动时强制指定:
php -d memory_limit=1G start.php start -d - 绝对不要设
-1——它不会让PHP无限用内存,反而触发OOM Killer杀进程,掩盖真实泄漏点
unset,重点查引用链终点——比如一个static数组里存着已关闭连接的句柄,或use ($this)捕获的服务容器里挂着未清理的Redis实例。










