进阶篇
约 1837 字大约 6 分钟
2025-07-07
PHP垃圾回收机制(GC)
- 使用 引用计数机制
- 将每个 PHP 变量保存在一个叫 zval 变量容器中。
- zval 变量容器 包含 变量的类型、变量值、 is_res、refcount
is_ref
用于标识该变量是否为引用集合或变量。
- refcount 表示指向当前变量的个数。
- 默认打开垃圾回收机制, 当发现有存在循环引用的zval时, 就会把其投入到根缓冲区, 当根缓冲区达到配置文件中的指定数量后, 就会进行垃圾回收, 以此解决8. 循环引用导致的内存泄露问题
- 如果引用计数减少到零, 所在变量容器将被清除(free), 不属于垃圾;
- 如果一个zval的引用计数减少后还大于0, 那么它会进入垃圾周期。
- 其次, 在一个垃圾周期中, 通过检查引用计数是否减1, 并且检查哪些变量容器的引用次数是零, 来发现哪部分是垃圾。
PHP底层原理
PHP代码执行过程:
- 启动 php 及 zend 引擎
- 加载注册拓展模块
- 对代码进行词法/语法分析
- 编译成opcode(opcache)
- 执行 opcode
PHP 的四层体系, 从下至上分为四层:
- Zend 引擎
- Zend 引擎整体用C语言实现,是 PHP 的内核部分,它负责将 PHP 代码翻译(词法、语法解析等一系列编译过程)为可执行的 opcode 操作码,并实现相应的处理方法、基本的数据结构(如 hashtable、OO)、内存分配及管理、提供相应的 API 方法供外部调用。
- 扩展层
- 围绕着 Zend 引擎,Extensions 通过组件化的方式提供各种基础服务,我们常见的各种内置函数(例如变量操作函数、字符串操作函数等)以及标准库等都是通过 Extensions 来实现。
- SAPI(服务器应用程序编程接口)
- SAPI 通过一系列钩子函数,使得 PHP 可以和外围交互数据,这是 PHP 非常优雅和成功的一个设计,通过 SAPI 成功的将 PHP 本身和上层应用解耦隔离,PHP 可以不再考虑如何针对不同应用进行兼容,而应用本身也可以针对自己的特点实现不同的处理方式。
- Application(上层应用)

PHP运行模式以及各自的原理
先了解一下 CGI :
- CGI(Common Gateway Interface)全称是“通用网关接口”,是一种让 客户端 与 Web服务器 程序进行通信(数据传输)的协议。
- CGI 用来规范 Web服务器 传输到 解析器(例: php-cgi) 中的数据类型以及数据格式,包括URL、查询字符串、POST数据、HTTP header等。
- 解析器只要符合 CGI 标准,就能作为一个 cgi 程序与 Web 服务器交互。
- 一次请求都要 fork 一个进程, 然后销毁,也就是(fork-and-execute)模式,性能较低。
PHP 运行模式:
FastCGI
- FastCGI(Fast Common Gateway Interface)全称是“快速通用网关接口”,也是一种让 客户端 与 Web服务器 程序进行通信(数据传输)的协议。。
FastCGI
是CGI
模式的升级版, 目的是避免重复解析配置文件和初始执行环境。- 像是一个常驻型
CGI
, 可以一直处理请求不结束该进程。 - 多进程,将比
CGI
消耗更多的服务器内存。 - 可平滑停止/启动进程。
PHPCGI
- 一个
CGI
程序,是 PHP 实现CGI
的 PHP解析器。 - 用于解析请求,返回结果。
- 不可平滑重启。
- 一个
PHP-FPM
PHP-FPM
为FastCGI
的进程管理器。- 工作原理为:
- Web 服务器启动时,加载启动
PHP-FPM
,PHP-FPM
读取配置文件,初始化运行环境。 PHP-FPM
创建一个 Master 主进程和若干个 Worker 进程,负责监听端口,等待接收请求,每个进程内都调用一个PHP-CGI
。- 用户发起请求, Web服务器接收请求并转发给
PHP-FPM
,空闲的 Worker 进程以抢占式的接收该请求。 - 监听接收后,
PHPCGI
解析请求,开始执行业务处理代码, 处理完成后,按照 CGI 规定的格式返给 Worker 进程, 然后退出进程, 此时 Worker 进程变成空闲状态等待下次请求。 - Worker 进程将结果返给 Web服务器, Web服务器接收返回内容并返回给客户端。
MODULE
apache + php
运行时,默认使用的是module 模式
,它把 php 作为apache
的模块随apache
启动而启动,接收到用户请求时则直接通过调用mod_php 模块
进行处理。
PHP-CLI
PHP-CLI 模式
属于命令行模式- 在终端直接输入
php 文件名.php
就可直接运行代码 - 没有超时时间
echo
、var_dump
、phpinfo
等输出会直接打印到控制台中
PHP 数组底层原理
底层实现是通过散列表(hash table) + 双向链表(解决hash冲突)
- hashtable:将不同的关键字(key)通过映射函数计算得到散列值(Bucket->h) 从而直接索引到对应的Bucket
- hash表保存当前循环的指针, 所以foreach 比for更快
- Bucket:保存数组元素的key和value, 以及散列值h
如何保证有序性
- 散列函数和元素数组(Bucket)中间添加一层大小和存储元素数组相同的映射表。
- 用于存储元素在实际存储数组中的下标
- 元素按照映射表的先后顺序插入实际存储数组中
- 映射表只是原理上的思路, 实际上并不会有实际的映射表, 而是初始化的时候分配Bucket内存的同时, 还会分配相同数量的 uint32_t 大小的空间, 然后将 arData 偏移到存储元素数组的位置。
解决hash重复(php使用的链表法):
- 链表法:不同关键字指向同一个单元时, 使用链表保存关键字(遍历链表匹配key)
- 开放寻址法:当关键字指向已经存在数据的单元的时候, 继续寻找其他单元, 直到找到可用单元(占用其他单元位置, 更容易出现hash冲突, 性能下降)
基础知识
- 链表:队列、栈、双向链表
- 链表:元素 + 指向下一元素的指针
- 双向链表:指向上一元素的指针 + 元素 + 指向下一元素的指针
依赖注入实现方式
- 构造函数依赖注入(如果依赖的类多,就会造成构造函数的形参特别多)
- set 方式注入(如果依赖的类多,那 set 的方法也特别多)
- 采用类似 Laravel 服务容器 实现依赖注入(调用时使用闭包,这样就做到 使用才实例化)
PHP 内存溢出解决
- 增加 PHP 可用内存大小
- 对大数组分批处理或 yield 处理
- 及时销毁大数组或变量
- 根据业务规则,尽可能的少用 静态变量
- 数据库操作完,及时关闭