写在前面
通过阅读本文,我们将一起了解通过了解中PHP中的两种内存管理的机制,进一步加深对于PHP中变量的理解,其中包括:
- PHP中两种垃圾收集的机制:
- reference counting;
- copy-on-write;
- 变量的赋值;
- 变量的引用;
- 变量范围(Scope);
Copy-on-write 机制
我们先来看一下PHP中简单的变量赋值是怎么样的:
$what = "Fred";
$what = 35;
$what = array('Fred', '35', 'Wilma');
PHP是一种弱类型语言,变量的申明没有那么严格,因此可以随意改变值的类型,这一点相信很多读者都已经了解了;
而这个特性的具体表现就不得不提到这一节的主角: symbol table;
在PHP中,一个变量包含两部分内容:
- Variable name, 变量名(e.g., $name);
- Variable value, 变量实际的值(e.g., “Fred”);
而 symbol table 就是用来保存 变量名 : 变量值在内存中的地址, 这一对映射关系的变化,接着我们通过几个例子来具体看一下:
$worker = array("Fred", 35, "Wilma");
$other = $worker;
其中:
- $worker 在赋值成功之后, symbol table 中就有了 worker 以及 数组的内存地址 的映射关系;
- 接着第二行代码,将 symbol table 中 other 所对应的内存地址指向 worker 映射的地址,而并不会开辟新的内存空间给 $other;
variable_name | variable_content_addr |
---|---|
worker | array_addr |
other | worker’s address |
$other[2] = "Smith"
但如果此时 $other 想要修改数组中的变量,PHP就会为 $other 分配新的地址空间来存储其指向的内容,即:
variable_name | variable_content_addr |
---|---|
worker | array_addr |
other | new_array_addr |
这里的原因就是PHP采取了第一种垃圾收集机制: copy-on-write;
- 即碰到多个项目拥有重复的内容时,先不进行duplicate复制,而是等到有一方需要修改数据时,才进行复制;
- 如此便可节省内存空间;
Reference counting
PHP中的reference引用
我们先来回顾一下PHP中的 reference, PHP官网给的定义如下:
“A PHP reference is an alias, which allows two different variables to write to the same value.”
官网说PHP中的reference是一种alias,即别名,就像一个人可以有很多称呼一样,一个内存中的值也可以有很多个变量名去表示:
$big_long_variable_name = "PHP";
$short =& $big_long_variable_name;
$big_long_variable_name .= " rocks!";
print "\$short is $short\n"; # $short is PHP rocks!
print "Long is $big_long_variable_name\n"; # Long is PHP rocks!
PHP会用 '==&'
表达式来将一个变量的引用赋值给另一个变量名,这样说还是不清晰,但我们前面已经了解了 symbol table, 那么我们就很容易来表示了:
variable_name | variable_content_addr |
---|---|
big_long_variable_name | variable_addr |
short | variable_addr |
也就是说,在 symbol table 中,两个变量名 big_long_variable_name 以及 short 都与同一个地址绑定了,如果其中的内容发生了改变,那么它们都会发生改变。
Variable Scope 变量范围
在我们介绍reference count之前,还需要一块拼图,就是变量的 Scope, 也就是变量能够作用的范围:
- Local scope
- PHP所定义的function中(包括嵌套的函数定义),所定义的变量,称为local variable,即本地变量;
- 这些变量只能在function本地被访问;
- Global scope
- Global 全局变量就是所有在function定义之外的变量,他们可以在PHP文件的任何地方被访问到;
- 在Function内部的Local scope中,可以使用global关键字来访问这些global变量: global $counter;
- 也可以使用超全局变量$GLOBALS来进行访问: $GLOBAL[counter];
- Static variables
- 方法中所定义的static 变量只在同一个function中可以被访问使用;
- Function parameters
- function所传入的参数也只能在function内部被使用
Reference counting
现在我们就可以来说 Reference counting 到底是干什么的了,PHP会为每一个(为变量)开辟的内存地址都分配一个计数器,来统计这个地址有几个别名alias,即有几个 reference: 比如我们上面提到的 short 以及 big_long_variable_name 都可以根据 symbol table 映射到 variable_addr, 那么 variable_addr 的 reference count
就是2,而一旦这个数字变成0,那么这个内存空间就会被释放。
我们还可以使用 isset()
来查看一个变量名所映射的地址是否有值;以及 unset()
来主动地解除这种映射,从而释放内存空间。
那我们之前说的 Variable scope 变量范围如何在这个机制中体现呢?
比如在下面的这个例子中:
- $x = 2的申明,让存储整型值 2 的地址的
reference count
加一了; - f(%x)的调用,生成了一个 function parameters scope的变量,同时生成了一个 reference 引用,再使
reference count
加一; - 而随着方法调用的结束,
$z
被释放,而其所指向的内存却不会被释放,而只是reference count
减一,还没有到零; - 而在整个php文件执行结束之后,
reference count
变为0,内存才会得到释放;
function f(& $z) {
$y = $z;
$z = $z + 2;
}
$x = 2;
f($x);
Reference
[1] ‘PHP: Objects and references - Manual’. https://www.php.net/manual/en/language.oop5.references.php.
[2] axiac, ‘Answer to “References are not pointer in php?”’, Stack Overflow, Sep. 28, 2017. https://stackoverflow.com/a/46462286/17534765.
[3] R. L. and K. Tatroe, ‘Programming PHP’, 1-56592-610-2. https://docstore.mik.ua/orelly/webprog/php/ch02_03.htm.