站長資訊網
        最全最豐富的資訊網站

        什么是引用計數?深入淺析PHP中的引用計數!

        什么是引用計數?怎么查看引用計數?怎么用引用計數?下面本篇文章就來帶大家了解一下引用計數,介紹一下引用計數的使用方法。

        什么是引用計數?深入淺析PHP中的引用計數!

        什么是引用計數

        在PHP的數據結構中,引用計數就是指每一個變量,除了保存了它們的類型和值之外,還額外保存了兩個內容,一個是當前這個變量是否被引用,另一個是引用的次數。為什么要多保存這樣兩個內容呢?當然是為了垃圾回收(GC)。

        也就是說,當引用次數為0的時候,這個變量就沒有再被使用了,就可以通過 GC 來進行回收,釋放占用的內存資源。

        任何程序都不能無限制的一直占用著內存資源,過大的內存占用往往會帶來一個嚴重的問題,那就是內存泄露,而 GC 就是PHP底層自動幫我們完成了內存的銷毀,而不用像 C 一樣必須去手動地 free 。

        怎么查看引用計數?

        我們需要安裝 xdebug 擴展,然后使用 xdebug_debug_zval() 函數就可以看到指定內存的詳細信息了,比如:

        $a = "I am a String"; xdebug_debug_zval('a'); // a: (refcount=1, is_ref=0)='I am a String'

        從上述內容中可以看出,這個 $a 變量的內容是 I am a String 這樣一個字符串。而括號中的 refcount 就是引用次數,is_ref 則是說明這個變量是否被引用。我們通過變量賦值來看看這個兩個參數是如何變化的。

        $b = $a; xdebug_debug_zval('a'); // a: (refcount=1, is_ref=0)='I am a String'  $b = &$a; xdebug_debug_zval('a'); // a: (refcount=2, is_ref=1)='I am a String'

        當我們進行普通賦值后,refcount 和 is_ref 沒有任何變化,但當我們進行引用賦值后,可以看到 refcount 變成了2,is_ref 變成了1。這也就是說明當前的

        a變量被引用賦值了,它的內存符號表服務于a 變量被引用賦值了,它的內存符號表服務于

        a 和 $b 兩個變量。

        $c = &$a; xdebug_debug_zval('a'); // a: (refcount=3, is_ref=1)='I am a String'  unset($c, $b); xdebug_debug_zval('a'); // a: (refcount=1, is_ref=1)='I am a String'  $b = &$a; $c = &$a; $b = "I am a String new"; xdebug_debug_zval('a'); // a: (refcount=3, is_ref=1)='I am a String new'  unset($a); xdebug_debug_zval('a'); // a: no such symbol

        繼續增加一個

        c的引用賦值,可以看到refcount會繼續增加。然后unsetc 的引用賦值,可以看到 refcount 會繼續增加。然后 unset 掉

        b 和 $c 之后,refcount 恢復到了1,不過這時需要注意的是,is_ref 依然還是1,也就是說,這個變量被引用過,這個 is_ref 就會變成1,即使引用的變量都已經 unset 掉了這個值依然不變。

        最后我們 unset 掉 $a ,顯示的就是 no such symbol 了。當前變量已經被銷毀不是一個可以用的符號引用了。(注意,PHP中的變量對應的是內存的符號表,并不是真正的內存地址)

        對象的引用計數

        和普通類型的變量一樣,對象變量也是使用同樣的計數規則。

        // 對象引用計數 class A{  } $objA = new A(); xdebug_debug_zval('objA'); // objA: (refcount=1, is_ref=0)=class A {  }  $objB = $objA; xdebug_debug_zval('objA'); // objA: (refcount=2, is_ref=0)=class A {  }  $objC = $objA; xdebug_debug_zval('objA'); // objA: (refcount=3, is_ref=0)=class A {  }  unset($objB); class C{  } $objC = new C; xdebug_debug_zval('objA'); // objA: (refcount=1, is_ref=0)=class A {  }

        不過這里需要注意的是,對象的符號表是建立的連接,也就是說,對

        objC進行重新實例化或者修改為NULL,并不會影響objC 進行重新實例化或者修改為 NULL ,并不會影響

        objA 的內容。對象進行普通賦值操作也是引用類型的符號表賦值,所以我們不需要加 & 符號。

        數組的引用計數

        // 數組引用計數 $arrA = [     'a'=>1,     'b'=>2, ]; xdebug_debug_zval('arrA'); // arrA: (refcount=2, is_ref=0)=array ( //     'a' => (refcount=0, is_ref=0)=1,  //     'b' => (refcount=0, is_ref=0)=2 // )  $arrB = $arrA; $arrC = $arrA; xdebug_debug_zval('arrA'); // arrA: (refcount=4, is_ref=0)=array ( //     'a' => (refcount=0, is_ref=0)=1,  //     'b' => (refcount=0, is_ref=0)=2 // )  unset($arrB); $arrC = ['c'=>3]; xdebug_debug_zval('arrA'); // arrA: (refcount=2, is_ref=0)=array ( //     'a' => (refcount=0, is_ref=0)=1,  //     'b' => (refcount=0, is_ref=0)=2 // )  // 添加一個已經存在的元素 $arrA['c'] = &$arrA['a']; xdebug_debug_zval('arrA'); // arrA: (refcount=1, is_ref=0)=array ( //     'a' => (refcount=2, is_ref=1)=1,  //     'b' => (refcount=0, is_ref=0)=2,  //     'c' => (refcount=2, is_ref=1)=1 // )

        調試數組的時候,我們會發現兩個比較有意思的事情。

        一是數組內部的每個元素又有單獨的自己的引用計數。這也比較好理解,每一個數組元素都可以看做是一個單獨的變量,但數組就是這堆變量的一個哈希集合。如果在對象中有成員變量的話,也是一樣的效果。當數組中的某一個元素被 & 引用賦值給其他變量之后,這個元素的 refcount 會增加,不會影響整個數組的 refcount 。

        二是數組默認上來的 refcount 是2。其實這是 PHP7 之后的一種新的特性,當數組定義并初始化后,會將這個數組轉變成一個不可變數組(immutable array)。為了和普通數組區分開,這種數組的 refcount 是從2開始起步的。當我們修改一下這個數組中的任何元素后,這個數組就會變回普通數組,也就是 refcount 會變回1。這個大家可以自己嘗試下,關于為什么要這樣做的問題,官方的解釋是為了效率,具體的原理可能還是需要深挖 PHP7 的源碼才能知曉。

        關于內存泄露需要注意的地方

        其實 PHP 在底層已經幫我們做好了 GC 機制就不需要太關心變量的銷毀釋放問題,但是,千萬要注意的是對象或數組中的元素是可以賦值為自身的,也就是說,給某個元素賦值一個自身的引用就變成了循環引用。那么這個對象就基本不太可能會被 GC 自動銷毀了。

        // 對象循環引用 class D{     public $d; } $d = new D; $d->d = $d; xdebug_debug_zval('d'); // d: (refcount=2, is_ref=0)=class D {  //     public $d = (refcount=2, is_ref=0)=...  // }  // 數組循環引用 $arrA['arrA'] = &$arrA; xdebug_debug_zval('arrA'); // arrA: (refcount=2, is_ref=1)=array ( //     'a' => (refcount=0, is_ref=0)=1,  //     'b' => (refcount=0, is_ref=0)=2,  //     'arrA' => (refcount=2, is_ref=1)=... // )

        不管是對象還是數組,在打印調試時出現了 … 這樣的省略號,那么你的程序中就出現了循環引用。在之前的文章 關于PHP中對象復制的那點事兒 中我們也講過這個循環引用的問題,所以這個問題應該是我們在日常開發中應該時刻關注的問題。

        總結

        引用計數是了解垃圾回收機制的前提條件,而且正是因為現代語言中都有一套類似的垃圾回收機制才讓我們的編程變得更加容易且安全。那么有人說了,日常開發根本用不到這些呀?用不到不代表不應該去學習,就像循環引用這個問題一樣,當代碼中充斥著大量的類似代碼時,系統崩潰只是遲早的事情,所以,這些知識是我們向更高級的程序進階所不可或缺的內容。

        測試代碼:https://github.com/zhangyue0503/dev-blog/blob/master/php/202004/source/PHP%E7%9A%84%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0%E6%98%AF%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D%EF%BC%9F.php  參考文檔: https://www.php.net/manual/zh/features.gc.refcounting-basics.php https://www.jianshu.com/p/52450a61354d

        本文轉載自:https://juejin.cn/post/6950093123682828301

        作者:硬核項目經理

        推薦學習:《PHP視頻教程》

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 亚洲高清专区日韩精品| 色久综合网精品一区二区| 亚洲欧美日韩精品久久亚洲区 | 亚洲AV无码成人精品区天堂| 成人国产精品日本在线观看| 国产精品ⅴ无码大片在线看| 中文字幕日本精品一区二区三区| 狠狠色伊人久久精品综合网| 亚洲综合一区二区国产精品| 中文字幕无码久久精品青草| 91不卡在线精品国产| 国产精品午睡沙发系列| 亚洲国产精品碰碰| 国内精品久久久久久久coent| 久久久91精品国产一区二区三区| 久久亚洲私人国产精品vA| 麻豆国内精品久久久久久| 国产精品热久久毛片| 一级成人精品h| 精品一区二区三区在线视频| 成人午夜精品网站在线观看 | 国产一区二区三区精品视频| 97精品人妻一区二区三区香蕉 | 91老司机深夜福利精品视频在线观看| 亚洲AV成人精品网站在线播放 | 91亚洲国产成人久久精品网址 | 国产一成人精品福利网站| 久久国产精品一国产精品金尊| 亚洲精品成人区在线观看| 久久精品国产福利国产琪琪| 国产玖玖玖九九精品视频| 国产精品JIZZ在线观看老狼| 国产精品欧美久久久久无广告| 亚洲精品欧美日韩| 91精品国产成人网在线观看| 一区二区三区精品国产欧美| 91国内揄拍国内精品对白不卡| 亚洲综合精品香蕉久久网97| 国产精品99| 久久久久人妻一区精品| 亚洲AV无码乱码精品国产|