摘要:本文將要講述發(fā)展歷程中的垃圾回收及內(nèi)存管理相關(guān)內(nèi)容,文末給出發(fā)展在各個(gè)階段有關(guān)內(nèi)存管理及垃圾回收內(nèi)核參考資料值得閱讀。引用計(jì)數(shù)在及以前的版本中,的垃圾回收采用的是引用計(jì)數(shù)算法。回收周期當(dāng)緩沖區(qū)滿時(shí),對緩沖區(qū)中的所有可能根進(jìn)行垃圾回收處理。
本文首發(fā)于 PHP 垃圾回收與內(nèi)存管理指引,轉(zhuǎn)載請注明出處。
本文將要講述 PHP 發(fā)展歷程中的垃圾回收及內(nèi)存管理相關(guān)內(nèi)容,文末給出 PHP 發(fā)展在各個(gè)階段有關(guān)內(nèi)存管理及垃圾回收(內(nèi)核)參考資料值得閱讀。
引用計(jì)數(shù)在 PHP 5.2 及以前的版本中,PHP 的垃圾回收采用的是 引用計(jì)數(shù) 算法。
引用計(jì)數(shù)基礎(chǔ)知識引用計(jì)數(shù)基礎(chǔ)知識
php 的變量存儲在「zval」變量容器(數(shù)據(jù)結(jié)構(gòu))中,「zval」屬性包含如下信息:
當(dāng)前變量的數(shù)據(jù)類型;
當(dāng)前變量的值;
用于標(biāo)識變量是否為引用傳遞的 is_ref 布爾類型標(biāo)識;
指向該「zval」變量容器的變量個(gè)數(shù)的 refcount 標(biāo)識符(即這個(gè) zval 被引用的次數(shù),注意這里的引用不是指引用傳值,注意區(qū)分)。
當(dāng)一個(gè)變量被賦值時(shí),就會生成一個(gè)對應(yīng)的「zavl」變量容器。
查看變量 zval 容器信息要查看變量的「zval」容器信息(即查看變量的 is_ref 和 refcount),可以使用 XDebug 調(diào)試工具的 xdebug_debug_zval() 函數(shù)。
安裝 XDebug 擴(kuò)展插件的方法可以查看 這個(gè)教程,有關(guān)XDebug 使用方法請閱讀 官方文檔。
假設(shè),我們已經(jīng)成功安裝好 XDebug 工具,現(xiàn)在就可以來對變量進(jìn)行調(diào)試了。
查看普通變量的 zval 信息
如果我們的 PHP 語句只是對變量進(jìn)行簡單賦值時(shí),is_ref 標(biāo)識值為 0,refcount 值為 1;若將這個(gè)變量作為值賦值給另一個(gè)變量時(shí),則增加 zval 變量容器的 refcount 計(jì)數(shù);同理,銷毀(unset)變量時(shí),「refcount」相應(yīng)的減去 1。
請看下面的示例:
寫時(shí)復(fù)制
寫時(shí)復(fù)制(Copy On Write:COW),簡單描述為:如果通過賦值的方式賦值給變量時(shí)不會申請新內(nèi)存來存放新變量所保存的值,而是簡單的通過一個(gè)計(jì)數(shù)器來共用內(nèi)存,只有在其中的一個(gè)引用指向變量的值發(fā)生變化時(shí),才申請新空間來保存值內(nèi)容以減少對內(nèi)存的占用。 - TPIP 寫時(shí)復(fù)制通過前面的簡單變量的 zval 信息我們知道 $copy 和 $name 共用 zval 變量容器(內(nèi)存),然后通過 refcount 來表示當(dāng)前這個(gè) zval 被多少個(gè)變量使用。
看個(gè)實(shí)例:
注意到?jīng)]有,當(dāng)將值 liugongzi handsome 賦值給變量 $copy 時(shí),name 和 copy 的 refcount 值都變成了 1,在這個(gè)過程中發(fā)生以下幾個(gè)操作:
將 $copy 從 $name 的 zval(內(nèi)從)中分離出來(即復(fù)制);
將 $name 的 refcount 減去 1;
對 $copy 的 zval 進(jìn)行修改(重新賦值和修改 refcount);
這里只是簡單對「寫時(shí)復(fù)制」進(jìn)行介紹,感興趣的朋友可以閱讀文末給出的參考資料進(jìn)行更加深入的研究。
查看引用傳遞變量的 zval 信息
引用傳值(&)的「引用計(jì)數(shù)」規(guī)則同普通賦值語句一樣,只是 is_ref 標(biāo)識的值為 1 表示該變量是引用傳值類型。
我們現(xiàn)在來看看引用傳值的示例:
復(fù)合類型的引用計(jì)數(shù)
與標(biāo)量類型(整型、浮點(diǎn)型、布爾型等)不同,數(shù)組(array)和對象(object)這種符合類型的引用計(jì)數(shù)規(guī)則會稍復(fù)雜一些。
為了更好的說明,還是先看看數(shù)組的引用計(jì)數(shù)示例:
$a = array( "meaning" => "life", "number" => 42 ); xdebug_debug_zval( "a" ); // a: // (refcount=1, is_ref=0) // array (size=2) // "meaning" => (refcount=1, is_ref=0)string "life" (length=4) // "number" => (refcount=1, is_ref=0)int 42上面的引用計(jì)數(shù)示意圖如下:
從圖中我們發(fā)現(xiàn)復(fù)合類型的引用計(jì)數(shù)規(guī)則基本上同標(biāo)量的計(jì)數(shù)規(guī)則一樣,就給出的示例來說,PHP 會創(chuàng)建 3 個(gè) zval 變量容器,一個(gè)用于存儲數(shù)組本身,另外兩個(gè)用于存儲數(shù)組中的元素。
添加一個(gè)已經(jīng)存在的元素到數(shù)組中時(shí),它的引用計(jì)數(shù)器 refcount 會增加 1。
$a = array( "meaning" => "life", "number" => 42 ); xdebug_debug_zval( "a" ); $a["life"] = $a["meaning"]; xdebug_debug_zval( "a" ); // a: // (refcount=1, is_ref=0) // array (size=3) // "meaning" => (refcount=2, is_ref=0)string "life" (length=4) // "number" => (refcount=0, is_ref=0)int 42 // "life" => (refcount=2, is_ref=0)string "life" (length=4)大致示意圖如下:
。
內(nèi)存泄露
雖然,復(fù)合類型的引用計(jì)數(shù)規(guī)則同標(biāo)量類型大致相同,但是如果引用的值為變量自身(即循環(huán)應(yīng)用),在處理不當(dāng)時(shí),就有可能會造成內(nèi)存泄露的問題。
讓我們來看看下面這個(gè)對數(shù)組進(jìn)行引用傳值的示例:
從內(nèi)存占用結(jié)果上看,雖然我們執(zhí)行了 unset($a) 方法來銷毀 $a 數(shù)組,但內(nèi)存并沒有被回收,整個(gè)處理過程的示意圖如下:
可以看到對于這塊內(nèi)存,再也沒有符合表(變量)指向了,所以 PHP 無法完成內(nèi)存回收,官方給出的解釋如下:
盡管不再有某個(gè)作用域中的任何符號指向這個(gè)結(jié)構(gòu) (就是變量容器),由于數(shù)組元素 “1” 仍然指向數(shù)組本身,所以這個(gè)容器不能被清除 。因?yàn)闆]有另外的符號指向它,用戶沒有辦法清除這個(gè)結(jié)構(gòu),結(jié)果就會導(dǎo)致內(nèi)存泄漏。慶幸的是,php 將在腳本執(zhí)行結(jié)束時(shí)清除這個(gè)數(shù)據(jù)結(jié)構(gòu),但是在 php 清除之前,將耗費(fèi)不少內(nèi)存。如果你要實(shí)現(xiàn)分析算法,或者要做其他像一個(gè)子元素指向它的父元素這樣的事情,這種情況就會經(jīng)常發(fā)生。當(dāng)然,同樣的情況也會發(fā)生在對象上,實(shí)際上對象更有可能出現(xiàn)這種情況,因?yàn)閷ο罂偸请[式的被引用。 - 摘自 官方文檔 Cleanup Problems簡單來說就是「引用計(jì)數(shù)」算法無法檢測并釋放循環(huán)引用所使用的內(nèi)存,最終導(dǎo)致內(nèi)存泄露。
引用計(jì)數(shù)系統(tǒng)的同步周期回收由于引用計(jì)數(shù)算法存在無法回收循環(huán)應(yīng)用導(dǎo)致的內(nèi)存泄露問題,在 PHP 5.3 之后對內(nèi)存回收的實(shí)現(xiàn)做了優(yōu)化,通過采用 引用計(jì)數(shù)系統(tǒng)的同步周期回收 算法實(shí)現(xiàn)內(nèi)存管理。引用計(jì)數(shù)系統(tǒng)的同步周期回收算法是一個(gè)改良版本的引用計(jì)數(shù)算法,它在引用基礎(chǔ)上做出了如下幾個(gè)方面的增強(qiáng):
引入了可能根(possible root)的概念:通過引用計(jì)數(shù)相關(guān)學(xué)習(xí),我們知道如果一個(gè)變量(zval)被引用,要么是被全局符號表中的符號引用(即變量),要么被復(fù)雜類型(如數(shù)組)的 zval 中的符號(數(shù)組的元素)引用,那么這個(gè) zval 變量容器就是「可能根」。
引入根緩沖區(qū)(root buffer)的概念:根緩沖區(qū)用于存放所有「可能根」,它是固定大小的,默認(rèn)可存 10000 個(gè)可能根,如需修改可以通過修改 PHP 源碼文件 Zend/zend_gc.c 中的常量 GC_ROOT_BUFFER_MAX_ENTRIES,再重新編譯。
回收周期:當(dāng)緩沖區(qū)滿時(shí),對緩沖區(qū)中的所有可能根進(jìn)行垃圾回收處理。
下圖(來自 PHP 手冊),展示了新的回收算法執(zhí)行過程:
引用計(jì)數(shù)系統(tǒng)的同步周期回收過程緩沖區(qū)(紫色框部分,稱為疑似垃圾),存儲所有可能根(步驟 A);
采用深度優(yōu)先算法遍歷「根緩沖區(qū)」中所有的「可能根(即 zval 遍歷容器)」,并對每個(gè) zval 的 refcount 減 1,為了避免遍歷時(shí)對同一個(gè) zval 多次減 1(因?yàn)椴煌母赡鼙闅v到同一個(gè) zval)將這個(gè) zvel 標(biāo)記為「已減」(步驟 B);
再次采用深度優(yōu)先遍歷算法遍歷「可能根 zval」。當(dāng) zval 的 refcount 值不為 0 時(shí),對其加 1,否則保持為 0。并請已遍歷的 zval 變量容器標(biāo)記為「已恢復(fù)」(即步驟 B 的逆運(yùn)算)。那些 zval 的 refcount 值為 0 (藍(lán)色框標(biāo)記)的就是應(yīng)該被回收的變量(步驟 C);
刪除所有 refcount 為 0 的可能根(步驟 D)。
整個(gè)過程為:
采用深度優(yōu)先算法執(zhí)行:默認(rèn)刪除 > 模擬恢復(fù) > 執(zhí)行刪除 達(dá)到內(nèi)存回收的目的。
優(yōu)化后的引用計(jì)數(shù)算法優(yōu)勢將內(nèi)存泄露控制在閥值內(nèi),這個(gè)由緩存區(qū)實(shí)現(xiàn),達(dá)到緩沖區(qū)大小執(zhí)行新一輪垃圾回收;
提升了垃圾回收性能,不是每次 refcount 減 1 都執(zhí)行回收處理,而是等到根緩沖區(qū)滿時(shí)才開始執(zhí)行垃圾回收。
你可以從 PHP 手冊 的回收周期 了解更多,也可以閱讀文末給出的參考資料。
PHP 7 的內(nèi)存管理PHP 5 中 zval 實(shí)現(xiàn)上的主要問題:
zval 總是多帶帶 從堆中分配內(nèi)存;
zval 總是存儲引用計(jì)數(shù)和循環(huán)回收 的信息,即使是整型(bool / null)這種可能并不需要此類信息的數(shù)據(jù);
在使用對象或者資源時(shí),直接引用會導(dǎo)致兩次計(jì)數(shù);
某些間接訪問需要一個(gè)更好的處理方式。比如現(xiàn)在訪問存儲在變量中的對象間接使用了四個(gè)指針(指針鏈的長度為四);
直接計(jì)數(shù)也就意味著數(shù)值只能在 zval 之間共享。如果想在 zval 和 hashtable key 之間共享一個(gè)字符串就不行(除非 hashtable key 也是 zval)。
PHP 7 中的 zval 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的調(diào)整:
最基礎(chǔ)的變化就是 zval 需要的內(nèi)存 不再是多帶帶從堆上分配,不再由 zval 存儲引用計(jì)數(shù)。
復(fù)雜數(shù)據(jù)類型(比如字符串、數(shù)組和對象)的引用計(jì)數(shù)由其自身來存儲。 - 摘自 Internal value representation in PHP 7 - Part 1【譯】這種實(shí)現(xiàn)的優(yōu)勢:
簡單數(shù)據(jù)類型不需要多帶帶分配內(nèi)存,也不需要計(jì)數(shù);
不會再有兩次計(jì)數(shù)的情況。在對象中,只有對象自身存儲的計(jì)數(shù)是有效的;
由于現(xiàn)在計(jì)數(shù)由數(shù)值自身存儲(PHP 有 zval 變量容器存儲),所以也就可以和非 zval 結(jié)構(gòu)的數(shù)據(jù)共享,比如 zval 和 hashtable key 之間;
間接訪問需要的指針數(shù)減少了。
更具體的有關(guān) PHP 7 zval 實(shí)現(xiàn)和內(nèi)存優(yōu)化細(xì)節(jié)可以閱讀 深入理解 PHP7 內(nèi)核之 zval 和 Internal value representation in PHP 7 - Part 1譯。
參考資料深入理解 PHP7 內(nèi)核之 zval
Internal value representation in PHP 7 - Part 1【譯】
Internal value representation in PHP 7 - Part 2【譯】
TPIP:第六節(jié) 寫時(shí)復(fù)制(Copy On Write)
TPIP:內(nèi)存管理
PHP7 內(nèi)核之 zval
淺談 PHP5 中垃圾回收算法 (Garbage Collection) 的演化
Confusion about PHP 7 refcount
引用計(jì)數(shù)系統(tǒng)中的同步周期回收 (Concurrent Cycle Collection in Reference Counted Systems) 論文
PHP7 革新與性能優(yōu)化
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/29180.html
JavaScript在創(chuàng)建變量(數(shù)組、字符串、對象等)是自動(dòng)進(jìn)行了分配內(nèi)存,而且當(dāng)它沒有被使用的狀態(tài)下,會自動(dòng)的釋放分配的內(nèi)容;其實(shí)這樣基層語言,如C語言,他們提供了內(nèi)存管理的接口,比如malloc()用于分配所需的內(nèi)存空間、free()釋放之前所分配的內(nèi)存空間?! ♂尫艃?nèi)存的過程稱為垃圾回收,例如avaScript這類高級語言可以提供了內(nèi)存自動(dòng)分配和自動(dòng)回收,其實(shí)這個(gè)自動(dòng)儲存不會占用太多空間...
摘要:垃圾回收器追蹤所有正在使用的對象,將無用對象標(biāo)記為垃圾。自動(dòng)化指針內(nèi)存回收自動(dòng)化的最好方式之一是使用鉤子函數(shù)。它們可能因?yàn)槎喾N原因發(fā)生,但是這種垃圾回收器是最主流的一種。 原文出處:What Is Garbage Collection? 一眼就應(yīng)該從名稱看出垃圾回收機(jī)制的含義-查找垃圾,然后丟棄。事實(shí)正好相反。垃圾回收器追蹤所有正在使用的對象,將無用對象標(biāo)記為垃圾。請留意,我們開始研究...
摘要:概要要理解的內(nèi)存管理策略,首先就要熟悉的運(yùn)行時(shí)數(shù)據(jù)區(qū),如上圖所示,在執(zhí)行程序的時(shí)候,虛擬機(jī)會把它所管理的內(nèi)存劃分為多個(gè)不同的數(shù)據(jù)區(qū),稱為運(yùn)行時(shí)數(shù)據(jù)區(qū)。 這是一篇有關(guān)JVM內(nèi)存管理的文章。這里將會簡單的分析一下Java如何使用從物理內(nèi)存上申請下來的內(nèi)存,以及如何來劃分它們,后面還會介紹JVM的核心技術(shù):如何分配和回收內(nèi)存。 JMM ( Java Memory Model )概要 show...
摘要:所有這些類型,在內(nèi)部統(tǒng)一用一個(gè)叫做的結(jié)構(gòu)表示,在源代碼中這個(gè)結(jié)構(gòu)名稱為。的具體定義在源代碼的文件中,下面是相關(guān)代碼的摘錄。 【轉(zhuǎn)】淺談PHP5中垃圾回收算法(Garbage Collection)的演化 前言 PHP是一門托管型語言,在PHP編程中程序員不需要手工處理內(nèi)存資源的分配與釋放(使用C編寫PHP或Zend擴(kuò)展除外),這就意味著PHP本身實(shí)現(xiàn)了垃圾回收機(jī)制(Garbage C...
摘要:引入的同步算法傳統(tǒng)上,像以前的用到的引用計(jì)數(shù)內(nèi)存機(jī)制,無法處理循環(huán)引用的內(nèi)存泄漏。然而使用文章引用計(jì)數(shù)系統(tǒng)中的同步周期回收中的同步算法,解決了這個(gè)內(nèi)存泄漏問題,這種算法就是的垃圾回收機(jī)制。 引用賦值 $a = apple; $b = &$a; 上述代碼中,我將一個(gè)字符串賦值給變量a,然后將a的引用賦值給了變量b。顯然,這個(gè)時(shí)候的內(nèi)存指向應(yīng)該是這樣的: $a -> apple
閱讀 1491·2019-08-30 15:44
閱讀 1954·2019-08-30 14:07
閱讀 2881·2019-08-30 13:56
閱讀 2350·2019-08-29 17:06
閱讀 1333·2019-08-29 14:13
閱讀 2091·2019-08-29 11:28
閱讀 3238·2019-08-26 13:56
閱讀 1954·2019-08-26 12:11