XAMPP Apache 双进程与内存占用过高排查处理方案

一、问题现象

  • 任务管理器中同时存在两个 Apache(httpd.exe)进程。
  • 其中一个进程占用内存约 7MB,另一个进程占用内存约 1.7G,整机内存占用偏高。
  • 环境为 Windows + XAMPP + Apache 2.4,PHP 以模块方式嵌入(mod_php)。

二、为什么会有两个 Apache 进程

Windows 版 Apache 使用 mpm_winnt 多处理模块,其进程模型固定为「父进程 + 子进程」两层,这是设计如此,不是重复启动或异常:

进程 职责 内存表现
父控制进程 监听端口、管理子进程生命周期、在子进程异常时重启它,本身不处理网页请求。 占用很小,约 7MB。
子工作进程 所有网页请求、PHP 脚本、加载的模块都在该进程内运行。 内存随请求持续累积,1.7G 的占用来自这个进程。

因此,两个进程本身无需处理;优化目标是控制子进程的内存累积与回收。

三、子进程内存持续上涨的原因

3.1 mod_php 嵌入式:内存不归还操作系统(最高发)

当前配置中 PHP 以模块方式直接加载进 Apache 子进程。PHP 解释器与 Apache 共享同一进程地址空间,脚本执行结束后,申请的内存通常只在进程内部复用,不会整体归还给操作系统。一旦代码存在静态变量、缓存未释放、循环读取大文件、数据库连接未释放等情况,进程 RSS 就会只涨不跌。

需要注意:php.ini 中的 memory_limit 只限制单次脚本可使用的最大内存,无法阻止 Apache 进程整体内存持续上涨。

3.2 子进程永不自动回收

MPM 配置不在主 httpd.conf,而由 Include conf/extra/httpd-mpm.conf 引入。mpm_winnt 默认 MaxConnectionsPerChild 0,表示子进程处理任意数量请求后都不重启,内存长期累积、无法被强制释放。

3.3 常驻模块与第三方插件

  • Apache 加载的各模块常驻在子进程内,基础内存随模块数量增加。
  • 配置末尾加载了云锁过滤模块 agent_apache_filter_2.4.dll,该模块同样注入子进程,会跟随一起占用内存,必要时可临时注释观察内存上涨速度。
  • 开启了 mod_http2(Protocols h2 http/1.1),在 Windows mpm_winnt 下部分场景会放大内存占用,访问量不大时可先关闭测试。

3.4 大请求、爬虫与导出类脚本

报表导出、一次性读取大文件、大数据量查询、高频爬虫访问,会一次性申请大量内存;即使脚本跑完,这部分内存也很难在进程内被回收利用。

3.5 OPcache 缓存占用

PHP OPcache 将大量脚本缓存进内存,这部分缓存同样计入 Apache 子进程内存;缓存配置过大时会进一步抬高基础占用。

四、排查步骤(按顺序执行)

  1. 确认 PHP 运行模式:查看 httpd.conf 与 httpd-xampp.conf 是否存在 LoadModule php* 这类加载 PHP 模块的行。存在即为 mod_php 嵌入式模式;当前 proxy_fcgi_module 处于注释状态,说明尚未使用 PHP-FPM。
  2. 重启 Apache 观察基线:重启服务后查看子进程内存是否回落到几十 MB。重启后缓慢上涨,说明是内存累积或泄漏;访问某个页面后立刻暴涨,则定位该页面的 PHP 代码。
  3. 查看活跃请求:配置已加载 status_module,可开启 server-status 页面,观察长时间挂住的请求,定位有问题的 URL 或脚本。

五、处理方案

方案 A:调整 MPM 自动回收(治标,最快见效)

打开 C:/xampp/apache/conf/extra/httpd-mpm.conf,找到 mpm_winnt 段:

<IfModule mpm_winnt_module>
    ThreadsPerChild      150
    MaxConnectionsPerChild   0
</IfModule>

修改前(默认)

改为:

<IfModule mpm_winnt_module>
    ThreadsPerChild      100
    MaxConnectionsPerChild   3000
</IfModule>

修改后(建议)

  • MaxConnectionsPerChild 3000:子进程处理 3000 次请求后,Apache 优雅地新建子进程、关闭旧进程,强制释放全部内存。
  • ThreadsPerChild 100:并发线程数,小网站 100 足够;线程过大反而会加快内存上涨。

数值参考:访问量不大用 2000~3000,访问量大可调到 5000;不要小于 1000,避免频繁重启造成卡顿。保存后重启 Apache。该方案只是自动回收内存,不能修复 PHP 代码中的内存泄漏。

方案 B:迁移到 PHP-FPM(治本,强烈推荐)

将 PHP 从 Apache 进程中剥离,由独立的 PHP-FPM 进程池运行,Apache 只负责静态文件、重写、SSL 与反向代理。这样 PHP 内存泄漏被隔离在 FPM 进程内,不会把 Apache 拖垮;FPM 自身也有进程回收机制。

  1. 在 httpd.conf 中取消注释代理模块:
    LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
  2. 在 httpd-xampp.conf 中注释掉加载 mod_php 的行(LoadModule php8_module 等)。
  3. 在虚拟主机配置中,将 PHP 请求通过 proxy_fcgi 转发给 PHP-FPM 监听端口。
  4. 使用 NTS 版 PHP 启动 php-fpm,并配置独立的进程池与回收参数。

方案 C:代码层面优化

  • 检查全局静态数组、无限追加的缓存、一次性读入内存的大文件。
  • 导出与大数据查询改为分页、流式输出。
  • 控制 OPcache 内存大小,不要配置过大。

方案 D:精简 Apache 模块

注释掉所有不使用的 LoadModule,减少常驻内存。当前配置中,不使用 WebDAV 可关闭 dav_lock_module,不使用服务器信息页可关闭 info_module。

六、结合当前配置的额外优化点

配置项 现状 建议
云锁过滤模块 LoadModule agent_apache_filter "...agent_apache_filter_2.4.dll",注入子进程常驻。 临时注释该行重启 Apache,对比内存上涨速度,判断是否由该插件加剧占用。
mod_http2 已加载并启用 Protocols h2 http/1.1。 访问量不大时可注释 http2_module 与 Protocols 行测试内存表现。
日志级别 LogLevel warn。 保持 warn,不要改为 debug,避免日志与额外开销暴涨。

七、建议执行顺序

  1. 先修改 httpd-mpm.conf 的 MaxConnectionsPerChild 3000,重启 Apache,观察几天内存走势。
  2. 观察内存是否在接近阈值后自动回落;若每次都涨到上限才回落,说明是 PHP 累积内存,代码存在泄漏。
  3. 长期方案:迁移 PHP-FPM,从架构上隔离 PHP 内存,避免 Apache 进程被 PHP 内存拖死。

若放任子进程内存持续累积,最终可能占满物理内存,导致系统卡顿、网站超时。

分享到:

用微信扫一扫,分享给朋友


发布日期:

所属分类: Apache/Nginx 标签: