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