對於PHP這種須要同時處理多個請求的程序來講,申請和釋放內存的時候應該慎之又慎,一不當心便會釀成大錯。另外一方面,除了要安全的申請和釋放內存外,還應該作到內存的最小化使用,由於它可能要處理每秒鐘數以千計的請求,爲了提升系統總體的性能,每一次操做都應該只使用最少的內存,對於沒必要要的相同數據的複製則應該能免則免。咱們來看下面這段PHP代碼:php
$a = "hello";html
$b = $a;安全
unset($a);函數
第一條語句執行後,PHP建立了$a這個變量,併爲它申請了12B的內存來存放"hello world"這個字符串(最後加個NULL字符,你懂的)。緊接着把$a賦給了$b,並釋放掉$a;性能
對於PHP來講,若是每一次變量賦值都執行一次內存複製的話,那須要額外申請12B的內存來存放這個重複的數據,固然爲了複製內存,還須要cpu執行某些計算,這固然會加劇cpu的負載。當第三句執行後,$a被釋放了,咱們剛纔的設想忽然變的這麼滑稽,此次賦值顯得好多餘哦。若是早就知道$a不用了,那咱們直接讓$b用$a的內存不就好了,還賦值幹嗎?若是你以爲12B沒什麼,那設想下若是$a是個10M的文件內容,或者20M,是否是咱們的計算機資源消耗的有點冤枉呢?優化
別擔憂,PHP很聰明!url
前面說過,PHP變量的名稱和值在內核中是保存在兩個不一樣的地方的,值是經過一個與名字毫無關係的zval結構來保存,而這個變量的名字a則保存在符號表裏,二者之間經過指針聯繫着。在咱們上面的例子裏,$a是一個字符串,咱們經過zend_hash_add把它添加到符號表裏,而後又把它賦值給$b,二者擁有相同的內容!若是二者指向徹底相同的內容,咱們有什麼優化措施嗎?spa
如今咱們檢查$a和$b兩個變量,他們的值指向了"Hello NowaMagic!"這個字符串在內存中的位置。可是在第三行:unset($a);這條語句釋放了$a。在這種狀況下,unset函數並不知道$a的值同時被$b用着,因此若是它直接釋放內存,則會致使$b的值也被清空了,從而致使邏輯錯誤,甚至可能會致使系統崩潰。指針
呵呵,其實你內心明白,PHP不會讓上述問題發生的!回顧一下zval的四個成員value、type、is_ref__gc、refcount__gc,咱們對value和type已經很熟了,如今則是後兩個成員發揮威力的時候了,這裏咱們主要講解refcount__gc這個成員。當一個變量被第一次建立的時候,它對應的zval結構體的refcount__gc成員的值會被初始化爲1,理由很簡單,由於只有這個變量本身在用它。可是當你把這個變量賦值給別的變量時,refcount__gc屬性便會加1變成2,由於如今有兩個變量在用這個zval結構了!orm
這個時候當咱們再用unset刪除$a的時候,它刪除符號表裏的$a的信息,而後清理它的值部分,這時它發現$a的值對應的zval結構的refcount值是2,也就是有另一個變量在一塊兒用着這個zval,因此unset只需把這個zval的refcount減去1就好了!
引用計數絕對是節省內存的一個超棒的模式!可是當咱們修改$b的值,並且還須要繼續使用$a時,該怎麼辦呢?
$a =1;
$b = $a;
$b += 5;
從代碼邏輯來看,咱們但願語句執行後$a仍然是1,而$b則須要變成6。咱們知道在第二句完成後內核經過讓$a和$b共享一個zval結構來達到節省內存的目的,可是如今第三句來了,這時$b的改變應該怎樣在內核中實現呢?
答案很是簡單,內核首先查看refcount__gc屬性,若是它大於1則爲這個變化的變量從原zval結構中複製出一份新的專屬與$b的zval來,並改變其值。如今$b變量擁有了本身的zval,而且能夠自由的修改它的值了。
若是用戶在PHP腳本中顯式的讓一個變量引用另外一個變量時,咱們的內核是如何處理的呢?
$a =1;
$b =&$a;
$b += 5;
做爲一個標準的PHP程序猿,咱們都知道$a的值也變成6了。當咱們更改$b的值時,內核發現$b是$a的一個用戶端引用,也就是所它能夠直接改變$b對應的zval的值,而無需再爲它生成一個新的不一樣與$a的zval。由於他知道$a和$b都想獲得此次變化!
可是內核是怎麼知道這一切的呢?簡單的講,它是經過zval的is_ref__gc成員來獲取這些信息的。這個成員只有兩個值,就像開關的開與關同樣。它的這兩個狀態表明着它是不是一個用戶在PHP語言中定義的引用。在第一條語句($a = 1;)執行完畢後,$a對應的zval的refcount__gc等於1,is_ref__gc等於0;。 當第二條語句執行後($b = &$a;),refcount__gc屬性向往常同樣增加爲2,並且is_ref__gc屬性也同時變爲了1!
最後,在執行第三條語句的時候,內核再次檢查$b的zval以肯定是否須要複製出一份新的zval結構來,此次不須要複製.
這一次,儘管它的refcount等於2,可是由於它的is_ref等於1,因此也不會被複制。內核會直接的修改這個zval的值。
咱們已經瞭解了php語言中變量的複製和引用的一些事,可是若是複製和引用這兩個事件被組合起來使用了該怎麼辦呢?看下面這段代碼:
$a = 1;
$b = $a;
$c = &$a;
這裏咱們能夠看到,$a,$b,$c這三個變量如今共用一個zval結構,有兩個屬於change-on-write組合($a,$c),有兩個屬於copy-on-write組合($a,$b),咱們的is_ref__gc和refcount__gc該怎樣工做,才能正確的處理好這段複雜的關係呢?
The answer is: 不可能!在這種狀況下,變量的值必須分離成兩份徹底獨立的存在!$a與$c共用一個zval,$b本身用一個zval,儘管他們擁有一樣的值,可是必須至少經過兩個zval來實現。見下圖【在引用時強制複製!】
一樣,下面的這段代碼一樣會在內核中產生歧義,因此須要強制複製!
$a = 1;
$b = &$a;
$c = $a;
須要注意的是,在這兩種狀況下,$b都與原初的zval相關聯,由於當複製發生時,內核還不知道第三個變量的名字。