搜尋
首頁後端開發php教程解析PHP5中的垃圾回收機制的演變

解析PHP5中的垃圾回收機制的演變

Aug 21, 2017 pm 01:52 PM
phpphp5回收

前言

PHP是一門託管型語言,在PHP程式設計中程式設計師不需要手動處理記憶體資源的分配與釋放(使用C編寫PHP或Zend擴充除外),這意味著PHP本身實作了垃圾回收機制(Garbage Collection)。現在如果去PHP官方網站(php.net)可以看到,目前PHP5的兩個分支版本PHP5.2和PHP5.3是分別更新的,這是因為許多項目仍然使用5.2版本的PHP,而5.3版本對5.2並不是完全相容。 PHP5.3在PHP5.2的基礎上做了許多改進,其中垃圾回收演算法就屬於一個比較大的改變。本文將分別討論PHP5.2和PHP5.3的垃圾回收機制,並討論這種演化和改進對於程式設計師編寫PHP的影響以及要注意的問題。

PHP變數及關聯記憶體物件的內部表示

垃圾回收說到底是對變數及其所關聯記憶體物件的操作,所以在討論PHP的垃圾回收機制之前,先簡單介紹PHP中變數及其記憶體物件的內部表示(其C原始碼中的表示)。

PHP官方文件中將PHP中的變數分成兩類:標量類型和複雜型別。標量類型包括布林型、整數、浮點型和字串;複雜型別包括陣列、物件和資源;還有一個NULL比較特殊,它不分為任何型別,而是單獨成為一類。

所有這些類型,在PHP內部統一用一個叫做zval的結構表示,在PHP原始碼中這個結構名稱為「_zval_struct」。 zval的具體定義在PHP原始碼的「Zend/zend.h」檔案中,以下是相關程式碼的摘錄。

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;
 
struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount__gc;
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};

其中聯合體「_zvalue_value」用來表示PHP中所有變數的值,這裡之所以使用union,是因為一個zval在一個時刻只能表示一個類型的變數。可以看到_zvalue_value中只有5個字段,但是PHP中算上NULL有8種資料型,那麼PHP內部是如何用5個字段表示8種類型呢?這算是PHP設計比較巧妙的一個地方,它透過複用字段達到了減少字段的目的。例如,在PHP內部布林型、整數及資源(只要儲存資源的識別碼即可)都是透過lval欄位儲存的;dval用於儲存浮點型;str儲存字串;ht儲存陣列(注意PHP中的陣列其實是哈希表);而obj儲存物件類型;如果所有欄位全部置為0或NULL則表示PHP中的NULL,這樣就達到了用5個欄位儲存8種類型的值。

而目前zval中的value(value的型別即是_zvalue_value)到底表示那種型,則由「_zval_struct」中的type決定。 _zval_struct即是zval在C語言中的具體實現,每個zval表示一個變數的記憶體物件。除了value和type,可以看到_zval_struct中還有兩個欄位refcount__gc和is_ref__gc,從其後綴可以斷定這兩個傢伙與垃圾回收有關。沒錯,PHP的垃圾回收全靠這倆欄位了。其中refcount__gc表示目前有幾個變數引用此zval,而is_ref__gc表示目前zval是否被按引用引用,這話聽起來很拗口,這和PHP中zval的「Write-On-Copy」機制有關,由於這個話題不是本文重點,因此這裡不再詳述,讀者只需記住refcount__gc這個字段的作用即可。

PHP5.2中的垃圾回收演算法——Reference Counting

PHP5.2中使用的記憶體回收演算法是大名鼎鼎的Reference Counting,這個演算法中文翻譯叫做“引用計數”,其想法非常直觀和簡潔:為每個內存對象分配一個計數器,當一個內存對象建立時計數器初始化為1(因此此時總是有一個變量引用此對象),以後每有一個新變量引用此內存對象,則計數器加1,而每當減少一個引用此記憶體物件的變數則計數器減1,當垃圾回收機制運作的時候,將所有計數器為0的記憶體物件銷毀並回收其佔用的記憶體。而PHP中記憶體物件就是zval,而計數器就是refcount__gc。

例如下面一段PHP程式碼示範了PHP5.2計數器的工作原理(計數器值透過xdebug得到):

<?php
 
$val1 = 100; //zval(val1).refcount_gc = 1;
$val2 = $val1; //zval(val1).refcount_gc = 2,zval(val2).refcount_gc = 2(因为是Write on copy,当前val2与val1共同引用一个zval)
$val2 = 200; //zval(val1).refcount_gc = 1,zval(val2).refcount_gc = 1(此处val2新建了一个zval)
unset($val1); //zval(val1).refcount_gc = 0($val1引用的zval再也不可用,会被GC回收)
 
?>
Reference Counting简单直观,实现方便,但却存在一个致命的缺陷,就是容易造成内存泄露。很多朋友可能已经意识到了,如果存在循环引用,那么Reference Counting就可能导致内存泄露。例如下面的代码:
<?php
$a = array();
$a[] = & $a;
unset($a);
 
?>

這段程式碼首先建立了陣列a,然後讓a的第一個元素按引用指向a,這時a的zval的refcount就變成2,然後我們銷毀變數a,此時a最初指向的zval的refcount為1,但是我們再也沒有辦法對其進行操作,因為其形成了一個循環自引用,如下圖所示:

解析PHP5中的垃圾回收機制的演變

其中灰色部分錶示已經不存在。由於a之前指向的zval的refcount為1(被其HashTable的第一個元素引用),這個zval就不會被GC銷毀,這部分記憶體就洩漏了。

这里特别要指出的是,PHP是通过符号表(Symbol Table)存储变量符号的,全局有一个符号表,而每个复杂类型如数组或对象有自己的符号表,因此上面代码中,a和a[0]是两个符号,但是a储存在全局符号表中,而a[0]储存在数组本身的符号表中,且这里a和a[0]引用同一个zval(当然符号a后来被销毁了)。希望读者朋友注意分清符号(Symbol)的zval的关系。

在PHP只用于做动态页面脚本时,这种泄露也许不是很要紧,因为动态页面脚本的生命周期很短,PHP会保证当脚本执行完毕后,释放其所有资源。但是PHP发展到目前已经不仅仅用作动态页面脚本这么简单,如果将PHP用在生命周期较长的场景中,例如自动化测试脚本或deamon进程,那么经过多次循环后积累下来的内存泄露可能就会很严重。这并不是我在耸人听闻,我曾经实习过的一个公司就通过PHP写的deamon进程来与数据存储服务器交互。

由于Reference Counting的这个缺陷,PHP5.3改进了垃圾回收算法。

PHP5.3中的垃圾回收算法——Concurrent Cycle Collection in Reference Counted Systems

PHP5.3的垃圾回收算法仍然以引用计数为基础,但是不再是使用简单计数作为回收准则,而是使用了一种同步回收算法,这个算法由IBM的工程师在论文Concurrent Cycle Collection in Reference Counted Systems中提出。

这个算法可谓相当复杂,从论文29页的数量我想大家也能看出来,所以我不打算(也没有能力)完整论述此算法,有兴趣的朋友可以阅读上面的提到的论文(强烈推荐,这篇论文非常精彩)。

我在这里,只能大体描述一下此算法的基本思想。

首先PHP会分配一个固定大小的“根缓冲区”,这个缓冲区用于存放固定数量的zval,这个数量默认是10,000,如果需要修改则需要修改源代码Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES然后重新编译。

由上文我们可以知道,一个zval如果有引用,要么被全局符号表中的符号引用,要么被其它表示复杂类型的zval中的符号引用。因此在zval中存在一些可能根(root)。这里我们暂且不讨论PHP是如何发现这些可能根的,这是个很复杂的问题,总之PHP有办法发现这些可能根zval并将它们投入根缓冲区。

当根缓冲区满额时,PHP就会执行垃圾回收,此回收算法如下:

1、对每个根缓冲区中的根zval按照深度优先遍历算法遍历所有能遍历到的zval,并将每个zval的refcount减1,同时为了避免对同一zval多次减1(因为可能不同的根能遍历到同一个zval),每次对某个zval减1后就对其标记为“已减”。

2、再次对每个缓冲区中的根zval深度优先遍历,如果某个zval的refcount不为0,则对其加1,否则保持其为0。

3、清空根缓冲区中的所有根(注意是把这些zval从缓冲区中清除而不是销毁它们),然后销毁所有refcount为0的zval,并收回其内存。

如果不能完全理解也没有关系,只需记住PHP5.3的垃圾回收算法有以下几点特性:

1、并不是每次refcount减少时都进入回收周期,只有根缓冲区满额后在开始垃圾回收。

2、可以解决循环引用问题。

3、可以总将内存泄露保持在一个阈值以下。

PHP5.2与PHP5.3垃圾回收算法的性能比较

由于我目前条件所限,我就不重新设计试验了,而是直接引用PHP Manual中的实验,关于两者的性能比较请参考PHP Manual中的相关章节:http://www.php.net/manual/en/features.gc.performance-considerations.php。

首先是内存泄露试验,下面直接引用PHP Manual中的实验代码和试验结果图:

<?php
class Foo
{
    public $var = &#39;3.1415962654&#39;;
}
 
$baseMemory = memory_get_usage();
 
for ( $i = 0; $i <= 100000; $i++ )
{
    $a = new Foo;
    $a->self = $a;
    if ( $i % 500 === 0 )
    {
        echo sprintf( &#39;%8d: &#39;, $i ), memory_get_usage() - $baseMemory, "\n";
    }
}
?>

解析PHP5中的垃圾回收機制的演變

解析PHP5中的垃圾回收機制的演變

可以看到在可能引发累积性内存泄露的场景下,PHP5.2发生持续累积性内存泄露,而PHP5.3则总能将内存泄露控制在一个阈值以下(与根缓冲区大小有关)。

另外是关于性能方面的对比:

<?php
class Foo
{
    public $var = &#39;3.1415962654&#39;;
}
 
for ( $i = 0; $i <= 1000000; $i++ )
{
    $a = new Foo;
    $a->self = $a;
}
 
echo memory_get_peak_usage(), "\n";
?>

这个脚本执行1000000次循环,使得延迟时间足够进行对比。

然后使用CLI方式分别在打开内存回收和关闭内存回收的的情况下运行此脚本:

time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
# and
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php

在我的机器环境下,运行时间分别为6.4s和7.2s,可以看到PHP5.3的垃圾回收机制会慢一些,但是影响并不大。

與垃圾回收演算法相關的PHP配置

可以透過修改php.ini中的zend.enable_gc來開啟或關閉PHP的垃圾回收機制,也可以透過呼叫gc_enable()或gc_disable()來打開或關閉PHP的垃圾回收機制。即使在PHP5.3中關閉了垃圾回收機制,PHP仍然會記錄可能根到根緩衝區,只是當根緩衝區滿額時,PHP不會自動運行垃圾回收,當然,任何時候您都可以透過手動呼叫gc_collect_cycles ()函數強制執行記憶體回收。

以上是解析PHP5中的垃圾回收機制的演變的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
PHP的當前狀態:查看網絡開發趨勢PHP的當前狀態:查看網絡開發趨勢Apr 13, 2025 am 12:20 AM

PHP在現代Web開發中仍然重要,尤其在內容管理和電子商務平台。 1)PHP擁有豐富的生態系統和強大框架支持,如Laravel和Symfony。 2)性能優化可通過OPcache和Nginx實現。 3)PHP8.0引入JIT編譯器,提升性能。 4)雲原生應用通過Docker和Kubernetes部署,提高靈活性和可擴展性。

PHP與其他語言:比較PHP與其他語言:比較Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能PHP與Python:核心功能Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP:網絡開發的關鍵語言PHP:網絡開發的關鍵語言Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP:許多網站的基礎PHP:許多網站的基礎Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

超越炒作:評估當今PHP的角色超越炒作:評估當今PHP的角色Apr 12, 2025 am 12:17 AM

PHP在現代編程中仍然是一個強大且廣泛使用的工具,尤其在web開發領域。 1)PHP易用且與數據庫集成無縫,是許多開發者的首選。 2)它支持動態內容生成和麵向對象編程,適合快速創建和維護網站。 3)PHP的性能可以通過緩存和優化數據庫查詢來提升,其廣泛的社區和豐富生態系統使其在當今技術棧中仍具重要地位。

PHP中的弱參考是什麼?什麼時候有用?PHP中的弱參考是什麼?什麼時候有用?Apr 12, 2025 am 12:13 AM

在PHP中,弱引用是通過WeakReference類實現的,不會阻止垃圾回收器回收對象。弱引用適用於緩存系統和事件監聽器等場景,需注意其不能保證對象存活,且垃圾回收可能延遲。

解釋PHP中的__ Invoke Magic方法。解釋PHP中的__ Invoke Magic方法。Apr 12, 2025 am 12:07 AM

\_\_invoke方法允許對象像函數一樣被調用。 1.定義\_\_invoke方法使對象可被調用。 2.使用$obj(...)語法時,PHP會執行\_\_invoke方法。 3.適用於日誌記錄和計算器等場景,提高代碼靈活性和可讀性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。