这篇文章,我们来实战操作一下扩展的内存管理,感受一下内存泄漏,加深对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
7PHP_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
4PHP_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
然后把扩展加入配置文件里面:
1extension=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进程的内存使用情况:
135247 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占的内存:
113713 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占的内存:
113713 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
4PHP_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进程占用的内存:
114470 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内存占用情况:
114470 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内存占用情况:
114470 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进程挂掉。
以上对malloc和emalloc的分析适用于FPM模式。但是对于Swoole这类扩展,接管了PHP的请求生命周期,所有对Swoole的请求都是在同一个请求生命周期里面,并且,这个请求生命周期一直不会结束。所以,就算我们使用了emalloc这类内存管理器,如果没有主动释放,也是会造成内存泄漏的,因为此时PHP的请求生命周期不会结束,因此PHP不会自己帮我们去释放这些内存。