php object 对象不存在。增加对象_深度好文:PHP写时拷贝与垃圾回收机制
寫入拷貝(Copy-on-write,簡稱COW)是一種計算機程序設計領域的優化策略。其核心思想是,如果有多個調用者(callers)同時要求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統才會真正復制一份專用副本(private copy)給該調用者,而其他調用者所見到的最初的資源仍然保持不變。這過程對其他的調用者都是透明的(transparently)。此作法主要的優點是如果調用者沒有修改該資源,就不會有副本(private copy)被創建,因此多個調用者只是讀取操作時可以共享同一份資源。
PHP中的COW
注意:以下代碼基于PHP5.6,PHP7之后引用計數機制有變化。
大家都知道,PHP是由C實現的,可是C是強類型語言,PHP怎么做到弱類型語言。一起來看下,PHP變量在C語言底層中的代碼:
typedef struct _zval_struct zval;typedef unsigned int zend_uint;typedef unsigned char zend_uchar; struct _zval_struct { zvalue_value value; /*注意這里,這個里面存的才是變量的值*/ zend_uint refcount__gc; /*引用計數*/ zend_uchar type; /* 變量當前的數據類型 */ zend_uchar is_ref__gc; /*變量是否引用*/};typedef union _zvalue_value { long lval; /*PHP中整型的值*/ double dval; /*PHP的浮點數值*/ struct { char *val; int len; } str; /*PHP的字符串*/ HashTable *ht; /*數組*/ zend_object_value obj; /*對象*/} zvalue_value;PHP的變量,低層是一個結構體zval,里面的zvalue_value結構體實際上是個聯合體,這個聯合體才是實際存放著PHP的變量值。 Zend引擎為了區別同一個zval地址是否被多個變量共享,引入了ref_count和is_ref兩個變量進行標識。
運行以下代碼,觀察變量refcount的變化:
<?php $foo = 1; xdebug_debug_zval('foo'); $bar = $foo; xdebug_debug_zval('foo'); $bar = 2; xdebug_debug_zval('foo'); ?> //-----執行結果----- foo: (refcount=1, is_ref=0)=1 foo: (refcount=2, is_ref=0)=1 foo: (refcount=1, is_ref=0)=1當$foo被賦值時,$foo變量的值的只由$foo變量指向。當?$foo的值被賦給?$bar時,PHP并沒有將內存復制一份交給$bar,而是把$foo和$bar指向一個地址, 同時引用計數增加1,也就是新的2。隨后,我們更改了$bar的值,這時如果直接需該$bar變量指向的內存,則?$foo的值也會跟著改變。這不是我們想要的結果。于是,PHP內核將內存復制出來一份,并將其值更新為賦值的:2(這個操作也稱為變量分離操作),同時原?$foo變量指向的內存只有$foo指向,所以引用計數更新為:refcount=1。
下面讓我們看一個查看內存的例子,可以更容易看到COW在內存使用優化方面的明顯作用:
<?php $j = 1; var_dump(memory_get_usage()); $tipi = array_fill(0, 100000, 'php-internal'); var_dump(memory_get_usage()); $tipi_copy = $tipi; var_dump(memory_get_usage()); foreach($tipi_copy as $i){ $j += count($i); } var_dump(memory_get_usage()); //-----執行結果----- $ php t.php int(630904) int(10479840) int(10479944) int(10480040)上面的代碼比較典型的突出了COW的作用,在數組變量$tipi被賦值給?$tipi_copy時,內存的使用并沒有立刻增加一半,在循環遍歷數?$tipi_copy時也沒有發生顯著變化,在這里$tipi_copy和?$tipi變量的數據共同指向同一塊內存,而沒有復制。
也就是說,即使我們不使用引用,一個變量被賦值后,只要我們不改變變量的值 ,也不會新申請內存用來存放數據。據此我們很容易就可以想到一些COW可以非常有效的控制內存使用的場景:只是使用變量進行計算而很少對其進行修改操作,如函數參數的傳遞,大數組的復制等等等不需要改變變量值的情形。
引用計數原理
了解了php變量的內部存儲結構之后,再了解下php變量賦值相關的原理和早期垃圾回收機制。
PHP5.2中使用的內存回收算法是大名鼎鼎的Reference Counting,這個算法中文翻譯叫做“引用計數”,其思想非常直觀和簡潔:為每個內存對象分配一個計數器,當一個內存對象建立時計數器初始化為1(因此此時總是有一個變量引用此對象),以后每有一個新變量引用此內存對象,則計數器加1,而每當減少一個引用此內存對象的變量則計數器減1,當垃圾回收機制運作的時候,將所有計數器為0的內存對象銷毀并回收其占用的內存。
內存泄漏
但是php5.3版本之前的垃圾回收機制存在一個漏洞,即當數組或對象內部子元素引用其父元素,而此時如果發生了刪除其父元素的情況,此變量容器并不會被刪除,因為其子元素還在指向該變量容器,但是由于所有作用域內都沒有指向該變量容器的符號,所以無法被清除,因此會發生內存泄漏,直到該腳本執行結束
如果你已經安裝了Xdebug,你能通過調用函數 xdebug_debug_zval()顯示”refcount”和”is_ref”的值。
舉例:
由于該示例不好輸出結果,用圖表示,如圖:
舉例:
unset($a);xdebug_debug_zval('a');如圖:
根緩沖機制
php5.3版本之后引入根緩沖機制,即php啟動時默認設置指定zval數量的根緩沖區(默認是10000),當php發現有存在循環引用的zval時,就會把其投入到根緩沖區,當根緩沖區達到配置文件中的指定數量(默認是10000)后,就會進行垃圾回收,以此解決循環引用導致的內存泄漏問題
為什么內存沒有全部收回來
因為php的核心結構Hashtable,在定義的時候不可能一次性分配足夠多的內存塊,所以初始化的時候只會分配一小塊,等不夠的時候在進行擴容,而Hashtable只擴容不減少,所以當存入100個變量的時候符號表不夠用了就進行一次擴容,當unset()時只是放了為變量值分配的內存,但是為變量名分配的內存還是在符號表中的,符號表并沒有縮小,所以沒收回來的內存是被符號表占去了。
php并不是只要內存不夠就去向OS申請內存,而是先申請一大塊內存,然后將其中一部分分給申請者,這樣再有邏輯需要申請內存的時候,就不需要再向OS申請內存了,避免了重復申請,只有當一大塊內存不夠用的時候再去申請。而當釋放內存時,php并非把內存還給了OS,而是把內存軌道自己維護的空閑內存列表,以便重復利用。
垃圾回收相關的配置
- zend.enable_gc,默認值為on,如果想關閉垃圾回收機制,可以設置為off
小知識點
- unset():unset()只是斷開一個變量到一塊內存區域的連接,同時將該內存區域的引用計數減1,內存是否回收主要還是看refcount是否到0了。
- null:將null賦值給一個變量是直接將該變量指向的數據結構置空,同時將其引用計數歸0。
- 腳本執行結束:該腳本中所有內存都會被釋放,無論是否有環引用。
總結
以上是生活随笔為你收集整理的php object 对象不存在。增加对象_深度好文:PHP写时拷贝与垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux普通用户命令权限,Linux普
- 下一篇: STM32的学习记录--1.准备工作