PHP的所有变量,都是以结构体zval来实现,在Zend/zend.h中我们能看到zval的定义:
typedef struct _zval_struct {
zvalue_value value; /* 变量的值 */
zend_uint refcount__gc; /* 引用计数器 */
zend_uchar type; /* 类型 */
zend_uchar is_ref__gc; /* 是否是引用 */
} zval;
php变量类型一共有8种
标准类型:布尔boolen, 整型integer, 浮点float, 字符string
复杂类型:数组array, 对象object
特殊类型:资源resource,null
其中refcount__gc和is_ref__gc表示变量是否是一个引用。type字段标识变量的类型,type的值可以是:IS_NULL, IS_BOOL, IS_LONG, IS_FLOAT, IS_STRING, IS_ARRAY, IS_OBJECT, IS_RESOURCE
。PHP根据type的类型,来选择如何存储到zvalue_value。 zvalue_value能够实现变量弱类型的核心,定义如下:
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len; /* this will always be set for strings */
} str; /* string (always has length) */
HashTable *ht; /* an array */
zend_object_value obj; /* stores an object store handle, and handlers */
} zvalue_value;
union 维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间
只关心二进制化的字符串,不关心具体格式.只会严格的按照二进制的数据存取。不会对某种特殊格式解析数据
。源类型是个很特殊的类型,zval.type=IS_RESOURCE,在PHP中有一些很难用常规类型描述的数据结构,比如文件句柄,对于C语言来说是 一个指针,不过PHP中没有指针的概念,也不能用常规类型来约束,因此PHP通过资源类型概念,把C语言中类似文件指针的变量,用zval结构来封装。资 源类型值是一个整数,ZE会根据这个值去资源的哈希表中获取。资源类型的定义:
typedefstruct_zend_rsrc_list_entry
{
void *ptr;
int type;
int refcount;
}zend_rsrc_list_entry;
其中,ptr是一个指向资源的最终实现的指针,例如一个文件句柄,或者一个数据库连接结构。type是一个类型标记,用于区分不同的资源类型。refcount用于资源的引用计数。
内核中,资源类型是通过函ZEND_FETCH_RESOURCE数获取的。
ZEND_FETCH_RESOURCE(con, type, zval *, default, resource_name, resource_type);
变量的类型依赖于zval.type字段指示,变量的内容按照zval.type存储到zval.value。当PHP中 需要变量的时候,只需要两个步骤:把zval.value的值或指针改变,再改变zval.type的类型。不过对于PHP的一些高级变量 Array/Object/Resource,变量转换要进行更多操作
php
$var = fopen('/tmp/aaa.txt', 'a'); // 资源 #1
$var = (int) $var;
var_dump($var); // 输出1
?>
PHP的变量符号表与zval值的映射,是通过HashTable(哈希表,又叫做散列表,下面简称HT),HashTable在ZE中广泛使用,包括常量、变量、函数等语言特性都是HT来组织,在PHP的数组类型也是通过HashTable来实现。
举个例子: php $var = 'Hello World';?>
var的变量名会存储在变量符号表中,代表var的类型和值的zval结构存储在哈希表中。内核通过变量符号表与zval地址的哈希映射,来实现PHP变量的存取。
为什么要提作用域呢?因为函数内部变量保护。按照作用域PHP的变量分为全局变量和局部变量,每种作用域PHP都会维护一个符号表的HashTable。当在PHP中创建一个函数或类的时候,ZE会创建一个新的符号表,表明函数或类中的变量是局部变量,这样就实现了局部变量的保护–外部无法访问函数内部的变量。 当创建一个PHP变量的时候,ZE会分配一个zval,并设置相应type和初始值,把这个变量加入当前作用域的符号表,这样用户才能使用这个变量。如果只是单纯的声明了一个变量,并没有赋值或初始化,此时var_dump会提示变量undefined,以及值为null
为了更好的理解变量的哈希表与作用域,举个简单的例子:
php
$temp = 'global';
function test() {
$temp = 'active';
}
test();
var_dump($temp);
?>
创建函数外的变量temp,会把这个它加入全局符号表,同时在全局符号表的HashTable中,分配一个字符类型的zval,值为‘global‘。创 建函数test内部变量$temp,会把它加入属于函数test的符号表,分配字符型zval,值为’active’ 。
CGI(Common Gateway Interface)全称是“通用网关接口”,WEB 服务器与WEB应用进行“交谈”的一种工具,其程序须运行在网络服务器上。CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php、perl、tcl等。
WEB服务器会传哪些数据给PHP解析器呢?URL、查询字符串、POST数据、HTTP header都会有。所以,CGI就是规定要传哪些数据,以什么样的格式传递给后方处理这个请求的协议。
也就是说,CGI就是专门用来和 web 服务器打交道的。web服务器收到用户请求,就会把请求提交给cgi程序(如php-cgi),cgi程序根据请求提交的参数作应处理(解析php),然后输出标准的html语句,返回给web服服务器,WEB服务器再返回给客户端,这就是普通cgi的工作原理。
CGI的好处就是完全独立于任何服务器,仅仅是做为中间分子。提供接口给apache和php。他们通过cgi搭线来完成数据传递。这样做的好处了尽量减少2个的关联,使他们2变得更独立。
但是CGI有个蛋疼的地方,就是每一次web请求都会有启动和退出过程,也就是最为人诟病的fork-and-execute模式,这样一在大规模并发下,就死翘翘了。
从根本上来说,FastCGI是用来提高CGI程序性能的。类似于CGI,FastCGI也可以说是一种协议。
FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次。它还支持分布式的运算, 即 FastCGI 程序可以在网站服务器以外的主机上执行,并且接受来自其它网站服务器来的请求。
FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中,并因此获得较高的性能。众所周知,CGI解释器的反复加载是CGI性能低下的主要原因,如果CGI解释器保持在内存中,并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail- Over特性等等
FastCGI与CGI特点:
PHP-CGI就是PHP实现的自带的FastCGI管理器。 虽然是php官方出品,但是这丫的却一点也不给力,性能太差,而且也很麻烦不人性化,主要体现在:
FPM (FastCGI 进程管理器)是一个 PHP 进程管理器,包含 master 进程和 worker 进程两种进程:master 进程只有一个,负责监听端口,接收来自 Web Server 的请求,而 worker 进程则一般有多个 (具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方
从 FPM 接收到请求,到处理完毕,其具体的流程如下:
主要优点有:
php7新增内容
编译原理的中间过程会产生一种中间代码(语言),PHP由Zend引擎(C语言编写)编译后的中间代码为Opcode然后再交由Zend引擎处理,如同C语言编译后汇编代码然后再交由汇编编译处理一样(也可以直接设置编译成二进制文件然后转为机器码),不是不可以直接生成机器码让计算机去执行,而是通过这个过程将复杂的问题分步进行,并且可以根据当前系统环境的不同而对Opcode做进一步的优化,一步一步地去解析和进行
$a = [123];
$a[] = &$a;
unset($a);
上面就是最简单的内存泄露的例子。当a自身引用的时候,会使得自身的refcount = 2,而当unset的时候,只会将refcount减为1,但此时已经没有符号指向这个zval了,可由于php认为这个zval还在被引用,因为它的refcount不为0,所以不会释放掉这块内存。这就造成了这块内存没法访问,同时又没有释放掉的问题。
php 5.3中引入的垃圾回收,就是为了解决这种问题的。(之前只有说refcount为0时进行内存释放)
对于scalar(标量)来说没有自身引用的问题,所以不用考虑垃圾回收。只需要对array和object类型进行考虑。参考源码将zval放入buffer的时候,也是会先判断是否为array或者object类,才将节点放入buffer中
只有在3这种情况下,GC才会把zval收集起来,然后判断这个zval是否为垃圾。下面就是 它具体的算法。
简单来说,gc会对gc_root_buffer中的zval的refcount进行减一的操作,如果减一后refcount为0,则该zval为垃圾。
详细步骤如下:
当缓冲区被节点塞满的时候
,GC才开始开始对缓冲区中的zval节点进行垃圾判断起初节点zval本身不做减1操作
,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作(php是如何进行环形引用的判别的
)这里应该和上面一样,当是环形引用的时候,需要对黑色节点再进行refcount+1
这个总结不错
对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作,之后如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。
这个道理其实很简单,假设数组a的refcount等于m, a中有n个元素又指向a,如果m等于n,那么算法的结果是m减n,m-n=0,那么a就是垃圾,如果m>n,那么算法的结果m-n>0,所以a就不是垃圾了
m=n代表什么? 代表a的refcount都来自数组a自身包含的zval元素,代表a之外没有任何变量指向它,代表用户代码空间中无法再访问到a所对应的zval,代表a是泄漏的内存,因此GC将a这个垃圾回收了
PHP中GC是默认开启的,同时gc_root_buffer大小为10000个节点
参考文章:https://blog.csdn.net/phpkernel/article/details/5734743
摘除几个有意思的点:
(unset后refcount不为0则进入buffer,需要注意zval在buffer中只会出现一次,所以会判断是否已出现)
,所以当unset的时候刚好buffer满了,那么这个unset会用时比较久,因为还要进行gc的处理先上结论,PHP中的clone为浅复制
$a = new A;
$b = $a;
这个时候我们知道,对a进行修改,b也会被修改,因为他们使用的是同一个zval。
这时有人就会用clone方法:
$a = new A;
$b = clone $a;
此时修改a的值,b的确不会受影响。但是如果是这样:
$a = new A;
$a->c = new C;
$b = $a;
你会发现非引用变量都不会受影响,但是c却还是和a的c一样。这就是clone的弊端,当a中有对其他对象的引用时(new返回的是引用),clone不会复制个新值,而是还和原引用一致,仍指向原来的对象
PHP中可以通过两种方式来实现深复制。第一种是__clone魔术方法:
public function __clone()
{
$this->workExperience = new WorkExperience();
}
深复制涉及深的层次,通过clone魔术方法实现需要知道有几层然后对每一层依次实现。
但这种方法会比较麻烦,因为需要手动对引用再进行clone
还有一种是可以通过序列化对象的方式,先将对象序列化之后再反序列化,如:
$ResumeB = unserialize(serialize($ResumeA));
此方案会触发被复制对象和所有被引用对象的__sleep和__wakeup魔术方法,所以这些情况都需要被考虑