久草视频2-久草视-久草社区视频-久草色在线-久草色视频-久草软件

各版本PHP的垃圾回收算法 | 張大奮的個人IT技術博客

我是創始人李巖:很抱歉!給自己產品做個廣告,點擊進來看看。  

摘要:垃圾回收算法在JAVA和.NET中早已是司空見慣的事情了,然后,PHP作為一門托管語言也有自己的垃圾回收算法,本文就PHP5.2和PHP5.3版本來詳細解說一下PHP的垃圾回收算法演變。

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,但是我們再也沒有辦法對其進行操作,因為其形成了一個循環自引用,如下圖所示:

其中灰色部分表示已經不復存在。由于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 = ’3.1415962654′;
}
$baseMemory = memory_get_usage();
for ( $i = 0; $i <= 100000; $i++ )
{
$a = new Foo;
$a->self = $a;
if ( $i % 500 === 0 )
{
echo sprintf( ’%8d: ’, $i ), memory_get_usage() – $baseMemory, ”\n”;
}
}
?>

可以看到在可能引發累積性內存泄露的場景下,PHP5.2發生持續累積性內存泄露,而PHP5.3則總能將內存泄露控制在一個閾值以下(與根緩沖區大小有關)。

另外是關于性能方面的對比:

< ?php
class Foo
{
public $var = ’3.1415962654′;
}
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()函數強制執行內存回收。

(若無特別注明, 張大奮的個人IT技術博客 文章皆為原創,轉載請注明出處)
原文鏈接: http://www.zhangdafen.com/archives/587

dafen @張大奮
最新發表
  • 20個實用的PHP庫(圖表、圖片、數據等)
  • 百度開發者中心已重磅推出了新服務——社會化服務組件!
  • 天上掉餡餅了! 兄弟連特訓班,首期免費!
  • 速成PHP自由建站 十天學會PHP –LAMP兄弟連
  • #520# 【程序員的告白】如果返回值為真,我將用盡一生去愛你;否則,我將釋放掉所有系統資源……
掃描雷鋒網微信二維碼
下載雷鋒網iOS客戶端

隨意打賞

提交建議
微信掃一掃,分享給好友吧。
主站蜘蛛池模板: 色综合天天综合 | 久久re视频精品538在线 | 胸奶好大好紧好湿好爽 | 奇米影视888四色首页 | 久久全国免费久久青青小草 | 538精品视频 | 国产三级跑 | 全黄h全肉细节文在线观看 全彩成人18h漫画 | 日本美女动态图片 | 成人免费体验区福利云点播 | 亚洲精品一线二线三线 | 久久内在线视频精品mp4 | 男同巨黄gay小说好爽 | 免费观看欧美性一级 | 欧美福利在线播放 | 久久91精品国产91久 | 9久re热视频这里只有精品 | 四虎在线成人免费网站 | 91久久精品国产一区二区 | 色综合伊人色综合网亚洲欧洲 | 星球大战成人h无删减版 | 亚洲女同一区二区 | 全日爱韩国视频在线观看 | 国产欧美日韩不卡一区二区三区 | 国产精品调教 | 爽好舒服使劲添高h视频 | 成3d漫二区三区四区 | 日韩精品成人在线 | 欧美乱理伦另类视频 | 亚洲精品九色在线网站 | 国产成人精品曰本亚洲77美色 | 日本人泡妞18xxⅹ | 無码一区中文字幕少妇熟女网站 | 超高清欧美同性videos | 草草在线视频 | 色戒完整版2小时38分钟 | 国产资源中文字幕 | 高清视频大片免费观看 | 国产1区精品 | 国产精品理论片 | 欧美黑人成人免费全部 |