在使用常驻内存框架需要注意什么?
-
内存释放
-
数据污染
-
资源释放
-
静态变量保存的对象不会被释放,需要手动管理
-
禁止使用exit和die
-
禁止使用sleep时间长的函数,防止导致Worker进程退出
-
不同Worker进程创建的对象和连接是不能互通的,创建连接池需要注意
-
定义公共函数的时候要使用 function_exists 判断函数是否存在,否则会提示重名
-
类引入文件需要使用 include_once 或者 require_once 否则会提示类名重复
-
修改代码需要重启服务才能生效
-
echo、print_r、var_dump不会输出到浏览器
-
使用$_GET、$_POST、$_REQUEST、$_SESSION、$_COOKIE、$_SERVER等$_开头的变量没有值
常驻内存和php-fpm模式有什么区别?
注意 | 常驻内存 | php-fpm |
变量 | 不释放 | 自动释放 |
对象 | 只new一次 | 每次请求都需要重新new |
单例 | 在初始化阶段把对象存到内存 | 每个请求重建创建和销毁对象 |
PHP 内存泄漏分析定位
- 场景一 程序操作数据过大
- 场景二 程序操作大数据时产生拷贝
- 场景三 配置不合理系统资源耗尽
- 场景四 无用的数据未及时释放
场景一:程序操作数据过大
情景还原:一次性读取超过php可用内存上限的数据导致内存耗尽
<?php
ini_set('memory_limit', '128M');
$string = str_pad('1', 128 * 1024 * 1024);
?>
问题现象:特定数据处理时可复现,做任何IO操作都有可能遇到此类问题,比如:一次mysql 查询返回大量数据、一次把大文件读取进程序等。
场景二:程序操作大数据时产生拷贝
情景还原:执行过程中对大变量进行了复制(COW机制),导致内存不够用。
<?php
ini_set("memory_limit",'1M');
$string = str_pad('1', 1* 750 *1024);
$string2 = $string;
$string2 .= '1';
问题现象:局部代码执行过程中占用内存翻倍。
问题分析:
php 是写时复制(Copy On Write),也就是说,当新变量被赋值时内存不发生变化,直到新变量的内容被操作时才会产生复制。
解决方法:
及早释放无用变量,或者以引用的形式操作原始数据。
<?php
ini_set("memory_limit",'1M');
$string = str_pad('1', 1* 750 *1024);
$string2 = $string;
unset($string);
$string2 .= '1';
场景三:配置不合理系统资源耗尽
- 情景还原:因配置不合理导致内存不够用,2G 内存机器上设置最大可以启动 100 个 php-fpm 子进程,但实际启动了 50 个 php-fpm 子进程后无法再启动更多进程
- 问题现象:线上业务请求量小的时候不出现问题,请求量一旦很大后部分请求就会执行失败
- 解决方法:合理设置 post_max_size、max_file_uploads、upload_max_filesize、max_input_vars、max_input_nesting_level 等参数并调优 php-fpm 相关参数。
场景四:无用的数据未及时释放
情景还原:这种问题从程序逻辑上不是问题,但是无用的数据大量占用内存导致资源不够用,应该有针对性的做代码优化。
问题现象:程序运行期间内存逐渐增长,程序结束后内存正常释放。
问题分析:
此类问题不易察觉,定位困难,尤其是有些框架封装好的方法,要明确其适用场景。
解决方法:
本例中要通过DB::listen方法获取所有执行的 SQL 语句记录并写入日志,但此方法存在内存泄露问题,在开发环境下无所谓,在生产环境下则应停用,改用其他途径获取执行的 SQL 语句并写日志。
场景五:清理内存碎片
即使unset
了所有变量,内存仍然无法降下去,代码如下
<?php
function main()
{
for ($i = 1; $i < 2000000; $i++) {
$GLOBALS[$i] = str_repeat("str_repeat这个函数会申请内存,但我马上就unset掉", 10);
}
for ($i = 1; $i < 2000000; $i++) {
unset($GLOBALS[$i]);
// 强制进行垃圾回收
}
}
echo "内存使用量:".memory_get_usage()."\n";
main();
echo "unset后内存使用量:".memory_get_usage()."\n";
if (gc_enabled()) {
echo "垃圾回收已开启。\n";
// 强制进行垃圾收集
gc_collect_cycles();
// 清理内存缓存
gc_mem_caches();
} else {
echo "垃圾回收未开启。\n";
}
echo "gc后内存使用量:".memory_get_usage()."\n";
?>
咋回事呢?根本原因是产生了内存碎片,和 PHP 的内存分配算法有关,这里不展开讲,大概原理是:小于 3072 字节的内存申请 PHP 会认为是小内存,PHP 会把所有申请的小内存块缓存起来,即使释放了也不归还给操作系统,以保证内存管理的效率
解决方法
最后如果遇到了内存碎片话问题,可以尝试用gc_mem_caches()函数,手动清除掉。