PHP的emalloc

这篇文章,我们来实战操作一下扩展的内存管理,感受一下内存泄漏,加深对PHP内存管理的理解。

首先,创建扩展目录:

1
~/codeDir/cCode/php-7.1.0/ext # ./ext_skel --extname=memory

然后进入目录:

1
~/codeDir/cCode/php-7.1.0/ext # cd memory/

替换文件config.m4为如下内容:

1
2
3
4
5
6
7
PHP_ARG_ENABLE(memory, whether to enable memory support,
Make sure that the comment is aligned:
[ --enable-memory Enable memory support])

if test "$PHP_MEMORY" != "no"; then
PHP_NEW_EXTENSION(memory, memory.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
fi

然后编辑文件memory.c里面的PHP_FUNCTION(confirm_memory_compiled)方法:

1
2
3
4
PHP_FUNCTION(confirm_memory_compiled)
{
void *foo = malloc(2 * 1024 * 1024);
}

这里,我们定义了一个PHP函数confirm_memory_compiled。它做的事情很简单,就是从堆中申请一块2M的内存,并且没有主动释放。

接着,编译、安装扩展:

1
2
~/codeDir/cCode/php-7.1.0/ext/memory # phpize ; ./configure
~/codeDir/cCode/php-7.1.0/ext/memory # make ; make install

然后把扩展加入配置文件里面:

1
extension=memory.so

然后确认是否安装扩展成功:

1
2
3
4
5
6
~/codeDir/cCode/php-7.1.0/ext/memory # php --ri memory

memory

memory support => enabled
~/codeDir/cCode/php-7.1.0/ext/memory #

然后编写测试脚本:

1
2
3
<?php
confirm_memory_compiled();

执行脚本:

1
2
~/codeDir/cCode/php-7.1.0/ext/memory # php memory.php
~/codeDir/cCode/php-7.1.0/ext/memory #

没有报错,说明我们的脚本正常执行了。

接着,我们启动一个PHP自带的服务器:

1
2
3
4
5
6
~/codeDir/cCode/php-7.1.0/ext/memory # php -S 127.0.0.1:80 -t ./
PHP 7.3.5 Development Server started at Mon Sep 23 13:08:13 2019
Listening on http://127.0.0.1:80
Document root is /root/codeDir/cCode/php-7.1.0/ext/memory
Press Ctrl-C to quit.

然后,另起一个终端,执行top命令,用来观察PHP进程的内存使用情况:

1
35247 root      20   0   27.8m  14.7m   0.0   0.7   0:00.02 S  `- php -S 127.0.0.1:80 -t ./

可以看到,在启动服务器的时候,PHP占用了27.8M的内存。

我们请求一次我们的服务器:

1
~/codeDir/cppCode/study # curl 127.0.0.1/memory.php

然后查看PHP占的内存:

1
13713 root      20   0   29.8m  14.9m   0.0   0.7   0:00.01 S  `- php -S 127.0.0.1:80 -t ./

我们发现PHP多占了2M的内存。

我们再请求一次:

1
~/codeDir/cppCode/study # curl 127.0.0.1/memory.php

然后再次查看PHP占的内存:

1
13713 root      20   0   31.8m  15.0m   0.0   0.8   0:00.03 S  `- php -S 127.0.0.1:80 -t ./

我们发现PHP又多占了2M的内存。

所以说,如果我们在一次请求的生命周期通过malloc分配了内存,但是没有释放,那么就会造成PHP整个生命周期的内存泄漏。

我们修改扩展函数:

1
2
3
4
PHP_FUNCTION(confirm_memory_compiled)
{
void *foo = emalloc(2 * 1024 * 1024);
}

然后,重新编译、安装扩展:

1
~/codeDir/cCode/php-7.1.0/ext/memory # make clean ; make ; make install

重新启动服务器:

1
2
3
4
5
6
~/codeDir/cCode/php-7.1.0/ext/memory # php -S 127.0.0.1:80 -t ./
PHP 7.3.5 Development Server started at Mon Sep 23 14:27:22 2019
Listening on http://127.0.0.1:80
Document root is /root/codeDir/cCode/php-7.1.0/ext/memory
Press Ctrl-C to quit.

此时,PHP进程占用的内存:

1
14470 root      20   0   27.8m  14.6m   0.0   0.7   0:00.01 S  `- php -S 127.0.0.1:80 -t ./

然后,我们请求一次服务器:

1
~/codeDir/cppCode/study # curl 127.0.0.1/memory.php

查看PHP内存占用情况:

1
14470 root      20   0   27.8m  15.2m   0.0   0.8   0:00.02 S  `- php -S 127.0.0.1:80 -t ./

发现,没有增长。

再次请求服务器:

1
~/codeDir/cppCode/study # curl 127.0.0.1/memory.php

再次查看PHP内存占用情况:

1
14470 root      20   0   27.8m  15.2m   0.7   0.8   0:00.03 S  `- php -S 127.0.0.1:80 -t ./

发现还是没有增长。

所以说,如果我们在一次请求的生命周期中通过emalloc分配了内存,但是没有释放,那么在PHP整个生命周期是不会造成内存泄漏的。因为在请求结束的时候,PHP会自动帮我们释放掉这些内存。但是,在一次请求中,如果一直不自己释放内存,那么这次请求很可能会内存不够,导致PHP进程挂掉。

以上对mallocemalloc的分析适用于FPM模式。但是对于Swoole这类扩展,接管了PHP的请求生命周期,所有对Swoole的请求都是在同一个请求生命周期里面,并且,这个请求生命周期一直不会结束。所以,就算我们使用了emalloc这类内存管理器,如果没有主动释放,也是会造成内存泄漏的,因为此时PHP的请求生命周期不会结束,因此PHP不会自己帮我们去释放这些内存。