前言:
大部分程式設計師看部落格可能不是太喜歡看漢字比較多的文章哈,但本文確實介紹以漢字為主描述,耐心看完,對大部分人來說一定會有收穫!
或許你知道,或許你不知道,PHP是個弱型,動態的腳本語言。所謂弱類型,就是說PHP並不嚴格驗證變數類型(嚴格來講,PHP是一個中強類型語言),在申明一個變數的時候,並不需要顯示指明它所保存的資料的型別。例如:$a = 1; (整形) $a ="1";(字串)
#一直使用PHP,但它究竟什麼,底層是怎麼實現才成就了PHP這樣方便快速的弱型別語言。
最近也查閱了很多書籍,還有相關部落格資料,了解到了許多關於PHP核心的一些機制。
php簡單的理解就是一個c語言的類別庫,你去php.net 下面下載一下它的原始碼就會發現,首先php的核心是zend engine ,它是一個用c語言寫的函數庫,用於處理底層的函數管理,記憶體管理,類別管理,和變數管理。在核心上面,他們寫了很多擴展,這些擴展大多數都是獨立的。用作業系統來比喻的話,zend
engine 就是一個作業系統,然後官方提供了很多“應用程式”,只是這個“應用程式” 不是media play 而是 mysql , libxml,dom。當然,你也可以根據zend engine 的api 開發自己的擴充。
#下邊開始介紹下PHP變數在內核中的儲存機制。
PHP是若型別語言,也就是說一個PHP變數可以保存任何的資料型別。但PHP是使用C語言寫的,而C語言是強類型語言是吧,每個變數都會有固定的類型,(一顆透過強型別轉變,不過有可能出現問題),那在Zend引擎中如何做到一個變數保存任何資料類型?下邊請看它儲存結構體。
開啟Zend/zend.h頭文件,會發現下列結構體#Zval:
1.zval結構
typedef struct _zval_struct zval;
typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val; //4字节 int len; //4字节 } str; HashTable *ht; /* hash table value */ zend_object_value obj; } zvalue_value;
struct _zval_struct { /* Variable information */ zvalue_value value; /* 变量值保存在这里 12字节*/ zend_uint refcount;//4字节,变量引用计数器 zend_uchar type; /* active type变量类型 1字节*/ zend_uchar is_ref;//是否变量被&引用,0表示非引用,1表示引用,1字节 };
2.zend_uchar
type
PHP中的變數包含四種複合型別(array, object)和兩個特殊的類型(resource 和NULL)。在zend內部,這些類型對應於下面的巨集(程式碼位置phpsrc/Zend/zend.h) Zend根據type值決定存取value的哪個成員,可用值如下:
#3.
zend_uint refcount__gc
symbols,所有的符號都存在符號表(symble table)中, 不同的作用域使用不同的符號表,關於這一點,我們之後會論述)指向該zval。在變數生成時,其refcount=1,典型的賦值運算如$a = $b會令zval的refcount加1,而unset操作會對應的減1。在PHP5.3之前,使用引用計數的機制來實作GC,如果一個zval的refcount較少到0,那麼Zend引擎會認為沒有任何變數指向該zval,因此會釋放該zval所佔的記憶體空間。但,事情有時並不會那麼簡單。後面我們會看到,單純的引用計數機制無法GC掉循環引用的zval(詳見後舉例3),即使指向該zval的變數已經被unset,從而導致了記憶體洩漏(Memory Leak)。 4.is_ref__gc
.如名字所示,ref_count__gc和is_ref__gc是PHP的GC機制所需的很重要的兩個字段,這兩個字段的值,可以透過xdebug等調試工具查看。
下面我們圍繞著zval,展開敘述,PHP變數到底是怎麼個儲存機制。
Xdebug的安裝我在前邊PHPstorm Xdebug調試也介紹過,這裡不贅述,請看: phpstorm+Xdebug斷點調試PHP
安装成功后,你的脚本中,可以通过xdebug_debug_zval打印Zval的信息,用法:
$var = 1; debug_zval_dump($var); $var_dup = $var; debug_zval_dump($var);
$a = 1; $b = $a; $c = $b; $d = &$c; // 在一堆非引用赋值中,插入一个引用
整个过程图示如下:
---------------------------------------------------------
$a = 1; $b = &$a; $c = &$b; $d = $c; // 在一堆引用赋值中,插入一个非引用
整个过程图示如下:
通过实例一、二,展现了,这就是PHP的copy on write写时分离机制、change
on write写时改变机制
过程:
PHP在修改一个变量以前,会首先查看这个变量的refcount,如果refcount大于1,PHP就会执行一个分离的例程,
对于上面的实例一代码,当执行到第四行的时候,PHP发现$c指向的zval的refcount大于1,那么PHP就会复制一个新的zval出来,将原zval的refcount减1,并修改symbol_table,使得$a,$b和$c分离(Separation)。这个机制就是所谓的copy on write(写时复制/写时分离)。把$d指向的新zval的is_ref的值 == 1 ,这个机制叫做change on write(写时改变)
结论:
分离指的是:分离两个变量存储的zval的位置,让分开不指向同一个空间! (那如何判定是否要分离呢,依据是什么?见下边)
改变指的是,有&引用赋值时,要把新开辟的zval 的 is_ref 赋值为1
判定是否分离的条件:如果is_ref =1 或recount == 1,则不分离
if((*val)->is_ref || (*val)->refcount<2){ //不执行Separation ... ;//process }
---------------------------------------------------------------------------------------------------
举例:
$a = $array('one'); $a[] = &$a; xdebug_debug_zval('a');
debug_zval_dump打印出zval的结构是:
a: (refcount=2, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=... )
上述输出中,…表示指向原始数组,因而这是一个循环的引用。如下图所示:
现在,我们对$a执行unset操作,这会在symbol table中删除相应的symbol,同时,zval的refcount减1(之前为2),也就是说,现在的zval应该是这样的结构:
unset($a); (refcount=1, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=... )
(应该ref_count=1)
(unset,其实就是打断$a在 符号表(symble table) 与zval 的一个指针映射关系。)
这时,不幸的事情发生了!
Unset之后,虽然没有变量指向该zval,但是该zval却不能被GC(指PHP5.3之前的单纯引用计数机制的GC)清理掉,$a 被释放,但是$a里的$a[1]也指向了该zval,它没有被释放,导致zval的refcount均大于0。这样,这些zval实际上会一直存在内存中,直到请求结束(参考SAPI的生命周期)。在此之前,这些zval占据的内存不能被使用,便白白浪费了,换句话说,无法释放的内存导致了内存泄露。
如果這種內存洩露僅僅發生了一次或者少數幾次,倒也還好,但如果是成千上萬次的內存洩露,便是很大的問題了。尤其在長時間運行的腳本中(例如守護程序,一直在後台執行不會中斷),由於無法回收內存,最終會導致系統“再無內存可用”,所以說,一定要避免這種操作。
垃圾回收機制:
1.php原來是透過引用計數器來實現記憶體回收,也就是多個php變數可能會引用同一份內存,這種情況unset掉其中一個是不會釋放記憶體的;
例如:$a
= 1; $b = $a; unset($a);//$a開啟的記憶體不會回收
2.離開了變數的作用域後變數所佔用的記憶體就會被自動清理(不包含靜態變量,靜態變數在腳本載入時創建,在腳本結束時釋放),
如函數或方法內的局部變量,對這些局部變數進行unset在函數外來看記憶體也是沒有減少的。
3.引用計數有個缺陷,就是當循環引用出現時,計數器無法清除0,記憶體佔用會持續到頁面存取結束。
在這個問題PHP5.3增加了垃圾回收機制。具體可以參考文件:http://www.php.cn/
垃圾回收機制是最早在Lisp中被提出,關於更多垃圾回收的資訊.
以上是PHP核心的儲存機制(分離/改變)的圖文程式碼詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!