摘要:中的引用引用可以通過不同的變量名,訪問同一個變量內容。在進行引用賦值后,等號左右兩邊的變量均變成了引用類型。緩沖區的作用就是減少垃圾回收算法運行的頻率,減少對正在運行的服務端代碼的影響。
baiyan
全部視頻:https://segmentfault.com/a/11...
原視頻地址:http://replay.xesv5.com/ll/26...
由于這個系列的視頻后面會再次細講垃圾回收,那么我們今天先復習一下PHP中的引用,為后面做一個鋪墊,后續的筆記會詳細講解垃圾回收器的相關運行原理。
PHP7中的引用引用:可以通過不同的變量名,訪問同一個變量內容。
PHP7中的引用通過讓兩個變量指向同一塊內存空間實現了上述特性。在進行引用賦值后,等號左右兩邊的變量均變成了引用類型(IS_REFERENCE)。這塊公用的內存空間就是PHP7為引用類型的變量專門創建的一個結構體,叫做zend_reference。
代碼示例:
$a = 1; echo $a; $b = &$a; //$b是$a的引用 echo $a; echo $b; unset($b); echo $a;
我們用gdb調試以上代碼:
首先執行$a = 1;并且打印$a的值,$a就是一個普通的zval,其類型是IS_LONG,很好理解:
執行關鍵的一步:$b = &$a,打印$a的值,觀察$a的存儲情況:
觀察上圖,可以發現$a的type變成了10 (IS_REFERENCE)類型,并且ref字段指向了一個新的結構體,這就是zend_reference,zend_reference中存儲著$a與$b共同的值1,由于$a與$b同時引用著這個結構體,故此時該結構體的refcount = 2。
接下來打印$b,觀察$b的存儲情況:
觀察上圖,發現與$b的type也是IS_REFERENCE類型,且ref字段也指向了一個zend_reference結構體,比較$a與$b指向的zend_reference,二者地址相同,說明指向了同一個zend_reference結構體。此時兩個變量的存儲情況如下圖所示:
接下來執行unset($b),觀察$a以及zend_reference的存儲情況,我們看是否符合預期:
我們看到unset($b)之后,$a所指向的zend_reference的refcount由2變為1,說明現在只有$a引用著這個結構體,b不再引用這個結構體,其類型變成了IS_UNDEF類型,代碼執行完畢。
那么我們看一下zend_reference結構體的基本結構:
struct _zend_reference { zend_refcounted_h gc; //gc相關,存有refcount zval val; //引用類型的變量值存在這個zval中的zend_value字段中。簡單類型的值直接存在這里,復雜類型的值存儲對應數據結構的指針,來找到這個變量的值,和之前講基本變量時候講過的一樣。 };
這個結構體一共只有2個字段,gc字段中是zend_refcounted_gc結構體類型,其中存儲了引用計數;val字段存儲了引用類型變量的值(簡單類型如整型、浮點型的值直接存在這里,復雜類型存對應數據結構的指針,與之前講基本變量的時候講過的一樣)。這樣相當于加了一個中間層,使得原始的zend_string或zend_array在內存中只有1份,方便管理與維護。
循環引用問題我們首先構造一個循環引用:
time()]; echo $a; $a[] = &$a; //循環引用 echo $a; unset($a); echo $a;
注意:由于開啟opcache的PHP7會在數組初始化的元素全部為常量元素的時候,將其優化成不可變數組(immutable array),這里的引用計數值refcount = 2只是一個偽引用計數,所以我們使用$a = ["time" => time()],讓其初始化后的refcount為正常的1。]見下圖:
利用gdb調試這段代碼:
執行完$a初始化并打印$a,refcount為1,type為7(IS_ARRAY)而此時的ref字段中的值是非法地址,說明此時還沒有生成中間的zend_reference結構體:
繼續執行下一行$a[] = &$a; 觀察下圖中綠色方框的含義:
- $a的zval中的ref指向zend_reference結構體 - zend_reference結構體中的zval字段中的arr指針指向了原始的zend_array - zend_array中的arData指針指向了bucket類型 - zend_array中的bucket數組元素也是一個IS_REFERENCE類型,它又指回到同一個zend_reference結構體:
根據gdb調試情況畫出內存結構圖:
由于有兩個東西指向zend_reference結構體(一個是$a,一個是$a數組中的一個元素),所以refcount = 2。原始的zend_array中也有一個refcount字段,由于只有一個zend_reference指向這個zend_array,所以refcount = 1。
接下來繼續執行unset($a):
我們可以看到,$a的type類型變成了0(IS_UNDEF),同時其指向的zend_reference結構體的refcount變為了1(因為$a數組中的元素仍然在指向它),我們畫圖來表示一下現在的內存情況:
那么問題出現了,$a是unset掉了,但是由于原始的zend_array中的元素仍然在指向仍然在指向zend_reference結構體,所以zend_reference的refcount是1,而并非是預期的0。這樣一來,這兩個zend_reference與zend_array結構在unset($a)之后,仍然存在于內存之中,如果對此不作任何處理,就會造成內存泄漏。
那么如何解決循環引用帶來的內存泄漏問題呢?垃圾回收就要派上用場了。在PHP7中,如果檢測到refcount -1 后仍 > 0的變量,會把它放入一個雙向鏈表中,等待垃圾回收,相當于一個緩沖區的作用。待緩沖區滿了之后(10000個存儲單元),然后再對其進行標記和清除(以后會在代碼層面具體講垃圾回收的方法)。
緩沖區的作用就是減少垃圾回收算法運行的頻率,減少對正在運行的服務端代碼的影響。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31313.html
摘要:此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考整理與心得體會,此文會不斷更新視頻傳送門每日學習記錄使用錄像設備記錄每天的學習源碼學習源碼學習內存管理筆記源碼學習內存管理筆記源碼學習內存管理筆記源碼學習基本變量筆記 此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考、整理與心得體會,此文會不斷更新 視頻傳送門:【每日學習記錄】使用錄像設備記錄每天的學習 PHP7...
摘要:以上詳細的講解請看源碼學習引用那么如何解決循環引用帶來的內存泄漏問題呢我們的垃圾回收就要派上用場了。接下來判斷如果垃圾回收器已經運行,那么本次就不再執行了。 baiyan 全部視頻:https://segmentfault.com/a/11... 垃圾回收觸發條件 我們知道,在PHP中,如果一個變量的引用計數減少到0(沒有任何地方在使用這個變量),它所占用的內存就會被PHP虛擬機自動回...
摘要:中以表示所有的變量,它是一個結構體。類型是一個聯合體,共占用。字段表示字符串的哈希值,在數組的中有用,方便快速定位。字段表示字符串長度這里就是一個柔性數組,在等源碼中也被大量使用。 baiyan 全部視頻:https://segmentfault.com/a/11... 源視頻地址:http://replay.xesv5.com/ll/24... 引入及基本概念 變量本質上就是給一段...
閱讀 3893·2021-11-17 09:33
閱讀 1201·2021-10-09 09:44
閱讀 403·2019-08-30 13:59
閱讀 3483·2019-08-30 11:26
閱讀 2185·2019-08-29 16:56
閱讀 2855·2019-08-29 14:22
閱讀 3154·2019-08-29 12:11
閱讀 1278·2019-08-29 10:58