核心内核:进程空间,就是操作系统给一个PHP进程(FPM子进程 / CLI进程)分配的独立虚拟内存地址空间。承接前面堆、栈、对象、zval整套知识:操作系统把内存隔离,每个进程拥有属于自己的一套内存地图,和别的进程互不干扰;PHP代码、加载的类、变量、堆、栈全部容纳在这片隔离的空间之内。类比:每一个PHP进程就是一栋独立楼房,楼房里面划分多个房间(内存分区);楼房之间墙壁隔离,A楼看不到B楼内部东西。
一、本源基础术语
- 进程 Process
操作系统资源调度的最小单位。启动php-fpm子进程,或者在命令行执行php xxx.php,就会创建一个PHP进程。拥有独立进程空间、文件描述符、PID。 - 进程地址空间(进程虚拟内存空间)
操作系统分配给进程的一整套连续虚拟地址。不是物理内存条真实地址,是虚拟映射。进程只能访问属于自己这片空间,不能直接读取其他进程内存。 - PID Process ID
进程编号,操作系统用来识别每一个进程。ps aux | grep php可以查看PHP进程PID。 - 虚拟地址
进程内部看到的内存编号。CPU通过页表映射到真实物理内存。 - 物理内存
服务器真实内存条硬件。多个进程的虚拟地址,可以映射到不同物理内存块。 - 内存隔离
操作系统核心机制。进程A无法读写进程B的内存,保证安全,单个进程崩溃一般不会直接干掉其他进程。
二、PHP进程空间内部五大内存分区
- 代码段(Text段 / 只读段)
存放编译后的PHP内核二进制、加载的扩展(pdo、redis等)。只读,运行期间不会修改。多个同类型进程可以共享这一块内存,节省物理内存。 - 全局数据段(Data / BSS段)
存放全局变量、静态变量、类静态属性。进程创建时就分配。BSS存放未初始化全局数据。类静态属性就保存在这个区域,不在堆、不在对象实例。 - 堆 Heap
动态内存区,向上增长。前面讲解的对象实例、哈希表、长字符串、资源全部放在堆。PHP内核通过内存池emalloc管理堆内存。受php.inimemory_limit限制。 - 栈 Stack
向下增长。保存函数调用栈帧、zval变量容器。函数调用就压入栈帧,函数执行结束栈帧自动释放;栈空间很小,递归太深会栈溢出。 - 共享库区域
系统动态库、PHP扩展so文件,多个进程可以共享加载同一份库文件。
一句话区分:代码段只读;全局段存静态全局;堆存动态创建对象;栈存临时函数变量。
三、FPM进程空间的生命周期术语
- FPM Master主进程
php-fpm启动,先创建Master主进程。负责读取配置、管理子进程、监听端口。Master本身不处理业务请求。 - FPM Worker子进程
Masterfork生成worker子进程,每个worker拥有独立进程空间。worker接收HTTP请求,执行PHP脚本。 - fork(进程复制)
linux下fork创建子进程:复制父进程内存空间(写时复制COW)。fork瞬间子进程和父进程内存一模一样;子进程修改内存,才会单独分配物理内存。 - 写时复制(COW,进程层面)
fork出来的worker,一开始和master共享物理内存。当worker执行代码、修改变量,操作系统才拷贝对应的内存页,分配独立物理内存。这是FPM节省内存的关键。 - 请求生命周期(单次HTTP请求)
worker进程空间复用:接收请求 → 在本进程空间内执行脚本,分配堆内存创建对象 → 请求结束,PHP释放本次请求堆内存,进程本身不销毁,进程空间保留,等待下一个请求。 - pm进程管理模式(FPM)
- static:固定数量worker
- dynamic:动态增减worker
- ondemand:有请求才创建worker
所有worker各自独立进程空间。
- 进程重启 recycle
FPM配置pm.max_requests,worker处理N次请求之后,自动退出销毁进程空间,master重新fork新worker。目的:清理长期运行累积的内存碎片、隐性内存泄漏。
四、进程空间内,PHP运行时相关术语
- 内存池(PHP own memory pool)
在进程的堆里面,PHP内核自建内存池。不每次直接调用系统malloc,从内存池分配小块内存;请求结束批量回收,减少系统调用开销。 - memory_limit
限制当前PHP进程空间中PHP脚本可使用的内存上限,不是整个进程全部内存。达到限制抛出内存耗尽致命错误。 - 全局符号表
保存在全局数据段。存放类定义、全局函数、常量。一旦类加载到进程,在这个worker进程生命周期内一直存在,不会随单次请求结束销毁。
重点:FPM worker处理下一个请求,类定义还在内存,不需要重新解析文件,这就是opcache的基础。
- OPcache
把PHP脚本编译后的opcode字节码缓存放在进程共享内存,避免重复解析编译PHP源码。属于进程间共享内存,不属于单个进程私有堆。 - 共享内存 shm
一块特殊内存,可以跨多个进程访问。Redis、IPC通信会用到,不属于某一个进程私有地址空间。
五、进程间通信 IPC
- IPC Inter-Process Communication
进程之间互相交换数据的方式。进程空间隔离,不能直接读写对方内存,必须走IPC。 - 管道pipe、消息队列、socket、shm共享内存
PHP多进程程序用来交换信息的IPC手段。FPM worker之间默认不通信。
六、进程空间 vs 线程空间(区分重点)
- 进程:独立完整地址空间;进程崩溃互不影响,开销大。
- 线程(轻量级进程)
一个进程内部可以开启多个线程,所有线程共用同一份进程地址空间。线程之间共享堆、全局数据;一个线程崩溃,整个进程直接挂掉。
PHP-FPM是多进程模型,不是多线程;Swoole可以选择多线程模式。
七、高频误区术语(面试高频)
- 误区:每次HTTP请求,都会新建一个PHP进程
纠正:FPM worker进程是复用的。进程只创建一次,反复处理多次请求,进程空间持续存在。直到达到max_requests才销毁重建。 - 误区:不同FPM worker之间可以直接访问对方的对象变量
纠正:进程空间隔离。worker1堆里的对象,worker2完全看不到。 - 误区:类定义每次请求都会重新加载
纠正:worker进程加载类后,类定义保存在进程全局段,请求结束不销毁,opcache进一步缓存字节码。 - 误区:memory_limit限制服务器整机内存
纠正:memory_limit是单个PHP进程的内存上限,不是整机。10个worker,每个限制128M,最大理论占用10*128M。 - 误区:fork之后父子进程内存完全独立
纠正:fork采用写时复制,创建瞬间共享物理内存,修改才拷贝。 - 误区:堆内存属于操作系统,不属于进程空间
纠正:堆是进程虚拟地址空间的一个分区,属于进程私有。
八、人生符号类比
一个PHP进程空间,就是一个独立的心智世界。每个世界有自己的仓库(堆)、临时工作台(栈)、固定规则模板(代码段、全局类定义)。多个心智世界互相隔离,不能直接偷看对方内部数据;fork相当于复制一份心智副本,副本不改东西就共用底层素材,一旦修改就分出独立版本;每次请求,只是在这个心智世界里面做一次任务,任务做完清空临时物品,但心智世界本身不会直接拆掉。
庖丁解牛一句话总结
PHP进程空间是操作系统分配给PHP进程的独立虚拟地址空间,分为代码段、全局段、堆、栈;FPM多进程模型每个worker拥有独立进程空间,天然内存隔离;fork使用写时复制减少内存开销;worker进程会复用,进程空间长期存活,单次请求结束只会释放本次业务堆内存,类定义保留在全局段;进程间不能直接互访内存,通信依靠IPC;进程和线程核心差别在于是否共享同一份地址空间。