首頁 >後端開發 >php教程 >PHP主|更好地了解PHP的垃圾收集

PHP主|更好地了解PHP的垃圾收集

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌原創
2025-02-26 08:33:13202瀏覽

PHP Master | Better Understanding PHP’s Garbage Collection

時間變遷,術語也隨之改變。如今,我們可能稱之為“PHP 資源回收”,而非“垃圾回收”。這更貼切地反映了其本質:並非簡單丟棄,而是重新利用不再使用的資源。然而,沿用“垃圾回收”這一歷史沿襲下來的名稱更為常見。

核心要點:

  • PHP 的垃圾回收機制分三個層次:作用域結束、引用計數和正式垃圾回收。作用域結束時,函數、腳本或會話中的資源會被清除。引用計數追踪使用某個變量的實體數量,計數為零時,變量被銷毀。 PHP 5.3 引入的正式垃圾回收機制則處理引用計數非零但可進一步遞減的情況。
  • PHP 的垃圾回收機制始終啟用,但可手動控制。可在 php.ini 文件中或使用 gc_enable() 和 gc_disable() 函數在腳本中禁用。 gc_collect_cycles() 函數允許手動啟動垃圾回收,根緩衝區大小可在 PHP 源代碼中修改。
  • 垃圾回收雖然有助於管理內存分配和防止內存洩漏,但其資源密集型特性也會影響性能。因此,應策略性地使用,尤其是在長時間運行的腳本或永不結束的腳本中,垃圾回收對於防止內存洩漏至關重要。
  • 良好的編程實踐有助於優化垃圾回收。這包括盡量減少或消除全局變量,將變量綁定到作用域,並註意數組嵌套或對象引用對象的情況,因為這些情況可能導致內存洩漏,也是正式垃圾回收機制的主要目標。

程序生成的垃圾

程序使用資源,有時是小資源,有時是大資源。例如數據字段。程序可能定義一個數據字段(例如序列號),並在程序中使用。一旦定義,此數據字段將佔用內存空間,可能只有幾字節,但仍然是空間。由於每台機器或編程環境都有有限的可用空間,剩餘空間將減少該字段佔用的空間量。當程序結束時,程序及其占用的任何空間都將消失,可用總空間將恢復到最大大小。但是,如果程序永不結束會發生什麼?我曾經編寫過一些此類程序。它們是美麗的傑作,每當車間裡的其他人注意到我創建了一個時,我總是很高興。沒有什麼比你自己讓一台大型IBM 計算機停機更能體現你的能力了,而周圍的隔間裡,一個人接一個人大聲說:“嘿,系統有什麼問題嗎?”訣竅是第二個或第三個加入,以轉移對你的注意力。但有些程序甚至旨在永遠運行,例如守護進程和其他此類程序。隨著它們的運行,它們產生的垃圾量可能會不斷增長。如果鎖定的資源很大,則會對系統產生真正的負面影響。因此,每種語言都必須有一種方法來清除孤立的資源,使它們可供其他用戶使用,並確保可用系統空間總量保持不變。幸運的是,PHP 採用三層方法進行垃圾清除。

第一層——作用域結束

首先,與大多數語言一樣,每當作用域結束時,該作用域內的所有內容都會被銷毀,任何已分配的資源都會被釋放。作用域可以涵蓋函數、腳本、會話等,當該作用域結束時,它所持有的所有內容也隨之結束。當然,您可以隨時使用 unset() 函數釋放資源。這就是函數和方法如此重要的原因之一,因為它們建立了作用域,規定了特定內存使用何時開始和何時結束,並限制了事物存在的時間。應盡可能使用它們,而不是全局實體。

第二層——引用計數

其次,與大多數腳本語言一樣,PHP 使用稱為引用計數的技術來跟踪有多少實體正在使用給定的變量。當在 PHP 腳本中創建變量時,PHP 會創建一個名為 zval 的小“容器”,該容器由分配給該變量的值加上另外兩條信息組成:is_ref 和 refcount。 zval 容器保存在表中,每個作用域(腳本、函數、方法等)都有一個表。 is_ref 是一個簡單的真/假值,指示變量是否是引用集的一部分,從而幫助 PHP 判斷這是一個簡單變量還是一個引用。 refcount 更有趣,因為它保存一個數值,指示有多少不同的變量正在使用此值。也就是說,如果您定義變量 $dave = 6,則 refcount 將設置為 1。如果我說 $programmer = $dave,則 refcount 將遞增到 2。 PHP 知道不要為值 6 創建第二個 zval;它只是更新已存在的值容器上的計數器。當程序結束時,或者當我們離開函數的作用域時,或者當使用 unset() 時,則此 refcount 將遞減。當 refcount 達到零時,zval 將被銷毀,它所持有的任何內存現在都已釋放。當然,這是一個簡單變量的簡單示例。當您談論數組或對象時,情況要復雜得多,因為將為數組中元素的多個值創建多個 zref,但基本處理過程相同。但是,如果我們在另一個數組中使用數組,這在更複雜的 PHP 腳本中經常發生,則會出現問題。在這種情況下,當設置原始數組值時,數組值的 refcount 設置為 1,然後當數組與另一個數組關聯時,refcount 遞增到 2。如果第二個數組的使用範圍結束,則 refcount 遞減 1。我們現在處於這樣一種情況:值本身不再與任何內容關聯,但表示它的容器 (zval) 的 refcount 仍然大於零。最終結果是,原始數組表示的存儲將不會被釋放,並且該內存量現在無法供任何內容使用。通常,我們認為這種丟失的存儲量很小,但通常並非如此。如今,數組可能是非常大的東西,如果發生這種情況的腳本是守護進程或其他幾乎連續運行的函數,則尤其成問題。在這種情況下,由此產生的“內存洩漏”可能會對性能甚至服務器的操作能力產生災難性的後果。

第三層——正式垃圾回收

顯然,基於引用計數的清除有其局限性,但幸運的是,PHP 5.3 提供了另一個選項來幫助解決這種情況。我們希望垃圾回收週期解決的特定情況是 zval 已遞減但仍為非零值的情況。基本上,循環查看哪些值可以進一步遞減,然後釋放值為零的值。實際發生的情況是 PHP 跟踪所有根容器 (zval)。無論垃圾回收是否開啟,都會執行此操作(因為它只需執行此操作,而無需詢問垃圾回收是否開啟,等等)。此根緩衝區最多可容納 10,000 個根(固定大小,但這可以更改)。當它填滿時,垃圾回收機制將啟動,並開始分析此緩衝區。 GC 例程首先要做的是遍歷根緩衝區並將所有 zval 計數遞減 1。在執行此操作時,它會使用類似複選標記的小標記標記每個標記,以便它只遞減一次根。然後,它再次遍歷並標記(這次使用一個小波浪線)所有已減少計數為零的 zval。非零的值將遞增,以便它們恢復其原始值。最後,它將再次滾動,從緩衝區中清除非零 zval,並釋放具有零 refcount 的值的存儲。 PHP 中始終啟用垃圾回收,但您可以在 php.ini 文件中使用指令 zend.enable_gc 關閉它。或者,您可以通過調用 gc_enable() 和 gc_disable() 函數在腳本中執行此操作。如上所述,如果啟用垃圾回收,則當根已滿時運行,但您可以覆蓋此設置並在您認為合適的時候使用 gc_collect_cycles() 函數運行回收。並且,您可以使用 PHP 源代碼中 zend/zend_gc.c 中的 gc_root_buffer_max_entries 值修改根緩衝區的大小。總而言之,這允許您控制 GC 是否運行以及何時何地運行,這是一件好事,因為它有點資源密集型,因此可能不是您隨意運行的那種東西。

何時使用它

由於垃圾回收會影響性能,因此值得花點時間確定何時應使用它。首先,請記住,除非您公開運行它(使用gc_collect_cycles() 函數),否則正式垃圾回收不會在根表(10,000 個條目)填滿之前發生,並且由於此表位於作用域級別,因此對於小型函數來說不會發生這種情況。您應該在小型腳本上使用它嗎?這取決於您。很難說運行垃圾回收之類的操作是一件壞事,但如果您有小型、快速運行的腳本,這些腳本啟動然後結束並消失,那麼可能不會有太多回報。但是,如果您的服務器運行許多保持持久的小型腳本,那麼它可能值得付出努力。唯一真正知道的方法是為您的應用程序設置基準並查看。當然,如果您有長時間運行的腳本,尤其是永不結束的腳本,那麼如果您想防止我們上面討論的那種內存洩漏,則垃圾回收至關重要。也許最重要的是,我們應該始終嘗試遵循良好的編程指南,以便我們最大限度地減少或消除全局變量,並將我們的變量綁定到作用域,以便即使我們有長時間運行的腳本,我們也可以在函數結束時釋放該內存,而不是腳本結束時。還要注意何時在數組中使用數組或對象引用對象,因為這種情況會導致內存洩漏,並且是正式垃圾回收過程的真正目標。

圖片來自 Fotolia

PHP 垃圾回收常見問題解答 (FAQ)

(此處省略FAQ部分,因為篇幅過長,且與偽原創目標不符。FAQ部分內容與原文高度重合,偽原創難度大,且修改後可能改變原意。)

以上是PHP主|更好地了解PHP的垃圾收集的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn