一直使用PHP,但它究竟什么,底層是怎么實現才成就了PHP這樣方便快捷的弱類型語言。
最近也查閱了很多書籍,還有相關博客資料,了解到了許多關于PHP內核的一些機制。
php簡單的理解就是一個c語言的類庫,你去php.net 下面下載一下它的源代碼就會發現,首先php的內核是zend engine ,它是一個用c語言寫的函數庫,用于處理底層的函數管理,內存管理,類管理,和變量管理。在內核上面,他們寫了很多擴展,這些擴展大多數都是獨立的。用操作系統來比喻的話,zend engine 就是一個操作系統,然后官方提供了很多“html' target='_blank'>應用程序”,只是這個“應用程序” 不是media play 而是 mysql , libxml,dom。當然,你也可以根據zend engine 的api 開發自己的擴展。
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字節 };
3.zend_uint refcount__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');
a: (refcount=2, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=...)
unset($a);(refcount=1, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=...)
這時,不幸的事情發生了!
Unset之后,雖然沒有變量指向該zval,但是該zval卻不能被GC(指PHP5.3之前的單純引用計數機制的GC)清理掉,$a 被釋放,但是$a里的$a[1]也指向了該zval,它沒有被釋放,導致zval的refcount均大于0。這樣,這些zval實際上會一直存在內存中,直到請求結束(參考SAPI的生命周期)。在此之前,這些zval占據的內存不能被使用,便白白浪費了,換句話說,無法釋放的內存導致了內存泄露。
如果這種內存泄露僅僅發生了一次或者少數幾次,倒也還好,但如果是成千上萬次的內存泄露,便是很大的問題了。尤其在長時間運行的腳本中(例如守護程序,一直在后臺執行不會中斷),由于無法回收內存,最終會導致系統“再無內存可用”,所以說,一定要避免這種操作。
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答