PHP 常驻内存泄漏分析定位

50 篇文章 0 订阅

在使用常驻内存框架需要注意什么?

  • 内存释放

  • 数据污染

  • 资源释放

  • 静态变量保存的对象不会被释放,需要手动管理

  • 禁止使用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()函数,手动清除掉。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值