PHP内存管理 垃圾回收
來源:http://www.jianshu.com/p/63a381a7f70c
概述
1) 操作系統(tǒng)直接管理著內(nèi)存,所以操作系統(tǒng)也需要進(jìn)行內(nèi)存管理,計(jì)算機(jī)中通常都有內(nèi)存管理單元(MMU) 用于處理CPU對內(nèi)存的訪問。
2) 應(yīng)用程序無法直接調(diào)用物理內(nèi)存, 只能向系統(tǒng)申請內(nèi)存。
向操作系統(tǒng)申請內(nèi)存空間會引發(fā)系統(tǒng)調(diào)用。
系統(tǒng)調(diào)用會將CPU從用戶態(tài)切換到內(nèi)核。
為了減少系統(tǒng)調(diào)用開銷。通常在用戶態(tài)進(jìn)行內(nèi)存管理。 申請大塊內(nèi)存?zhèn)溆谩J褂猛甑膬?nèi)存不馬上釋放,將內(nèi)存復(fù)用,避免多次內(nèi)存申請和釋放所帶來性能消耗。
3) PHP不需要顯示內(nèi)存管理,由Zend引擎進(jìn)行管理。
PHP內(nèi)存限制
1)php.ini中的默認(rèn)32MB
memory_limit = 32M
2)動(dòng)態(tài)修改內(nèi)存
ini_set ("memory_limit", "128M")
3)獲取目前內(nèi)存占用
memory_get_usage() : 獲取PHP腳本所用的內(nèi)存大小
memory_get_peak_usage() :返回當(dāng)前腳本到目前位置所占用的內(nèi)存峰值。
學(xué)習(xí)內(nèi)存管理的目的
了解PHP如何占用內(nèi)存,可以避免不必要的內(nèi)存浪費(fèi)。
PHP中的內(nèi)存管理
包含:
1)足夠內(nèi)存
2)可用內(nèi)存獲取部分內(nèi)存
3)使用后的內(nèi)存,是否銷毀還是重新分配
PHP內(nèi)存管理器
clipboard.png
接口層,是一些宏定義。
堆層 heap
_zend_mm_heap
初始化內(nèi)存,調(diào)用 zend_mm_startup
PHP內(nèi)存管理維護(hù)三個(gè)列表:
1)小塊內(nèi)存列表 free_buckets
2)大塊內(nèi)存列表 large_free_buckets
3)剩余內(nèi)存列表 rest_buckets
兩個(gè)HashTable 結(jié)構(gòu),難點(diǎn)是查找和計(jì)算內(nèi)存地址
1)free_buckets
Hash函數(shù)為:
2)large_free_buckets
Hash函數(shù)為:
存儲層 storage
- 內(nèi)存分配的方式對堆層透明化,實(shí)現(xiàn)存儲層和heap層的分離。
- 不同的內(nèi)存分配方案, 有對應(yīng)的處理函數(shù)。
內(nèi)存的申請
PHP底層對內(nèi)存的管理, 圍繞著小塊內(nèi)存列表(free_buckets)、 大塊內(nèi)存列表(large_free_buckets)和 剩余內(nèi)存列表(rest_buckets)三個(gè)列表來分層進(jìn)行的
ZendMM向系統(tǒng)進(jìn)行的內(nèi)存申請,并不是有需要時(shí)向系統(tǒng)即時(shí)申請, 而是由ZendMM的最底層(heap層)先向系統(tǒng)申請一大塊的內(nèi)存,通過對上面三種列表的填充, 建立一個(gè)類似于內(nèi)存池的管理機(jī)制。 在程序運(yùn)行需要使用內(nèi)存的時(shí)候,ZendMM會在內(nèi)存池中分配相應(yīng)的內(nèi)存供使用。 這樣做的好處是避免了PHP向系統(tǒng)頻繁的內(nèi)存申請操作
ZendMM對內(nèi)存分配的處理步驟:
1)內(nèi)存檢查;
2)命中緩存,找到內(nèi)存塊,調(diào)至步驟5;
3)在ZendMM管理的heap層存儲中搜索合適大小的內(nèi)存塊, 是在三種列表中小到大進(jìn)行的,找到block后,調(diào)至步驟5;
4)步驟3未找到內(nèi)存,則使用 ZEND_MM_STORAGE_ALLOC 申請新內(nèi)存塊 (至少為ZEND_MM_SEG_SIZE),進(jìn)行步驟6
5)使用zend_mm_remove_from_free_list函數(shù)將已經(jīng)使用block節(jié)點(diǎn)在zend_mm_free_block中移除;
6) 內(nèi)存分配完畢,對zend_mm_heap結(jié)構(gòu)中的各種標(biāo)識型變量進(jìn)行維護(hù),包括large_free_buckets, peak,size等;
7) 返回分配的內(nèi)存地址;
PHP內(nèi)存管理器
內(nèi)存的銷毀
ZendMM在內(nèi)存銷毀的處理上采用與內(nèi)存申請相同的策略,當(dāng)程序unset一個(gè)變量或者是其他的釋放行為時(shí), ZendMM并不會直接立刻將內(nèi)存交回給系統(tǒng),而是只在自身維護(hù)的內(nèi)存池中將其重新標(biāo)識為可用, 按照內(nèi)存的大小整理到上面所說的三種列表(small,large,free)之中,以備下次內(nèi)存申請時(shí)使用。
ZendMM將內(nèi)存塊以整理收回到zend_mm_heap的方式,回收到內(nèi)存池中。
程序使用的所有內(nèi)存,將在進(jìn)程結(jié)束時(shí)統(tǒng)一交還給系統(tǒng)。
垃圾回收
自動(dòng)回收內(nèi)存的過程叫垃圾收集。PHP提供了語言層的垃圾回收機(jī)制,讓程序員不必過分關(guān)心程序內(nèi)存分配。
PHP5.3之前
引用計(jì)數(shù)方式的內(nèi)存動(dòng)態(tài)管理。
PHP中所有的變量都是以zval變量的形式存在。
變量引用計(jì)數(shù)變?yōu)?時(shí),PHP將在內(nèi)存中銷毀這個(gè)變量。只是這里的垃圾并不能稱之為垃圾。并且PHP在一個(gè)生命周期結(jié)束后就會釋放此進(jìn)程/線程所占的內(nèi)容,這種方式?jīng)Q定了PHP在前期不需要過多考慮內(nèi)存的泄露問題。
PHP5.3的垃圾回收
引入垃圾收集機(jī)制的目的是為了打破引用計(jì)數(shù)中的循環(huán)引用,從而防止因?yàn)檫@個(gè)而產(chǎn)生的內(nèi)存泄露。 垃圾收集機(jī)制基于PHP的動(dòng)態(tài)內(nèi)存管理而存在。PHP5.3為引入垃圾收集機(jī)制,在變量存儲的基本結(jié)構(gòu)上有一些變動(dòng).
struct _zval_struct {/* Variable information */ zvalue_value value;/* value */ zend_uint refcount__gc; zend_uchar type;/* active type */ zend_uchar is_ref__gc; };添加了 __gc 以用于新的垃圾回收機(jī)制。
PHP5.3中的垃圾回收算法——Concurrent Cycle Collection in Reference Counted Systems
PHP5.3的垃圾回收算法仍然以引用計(jì)數(shù)為基礎(chǔ),但是不再是使用簡單計(jì)數(shù)作為回收準(zhǔn)則,而是使用了一種同步回收算法,這個(gè)算法由IBM的工程師在論文Concurrent Cycle Collection in Reference Counted Systems中提出。
論文較復(fù)雜, 列出一些大體描述。
首先PHP會分配一個(gè)固定大小的“根緩沖區(qū)”,這個(gè)緩沖區(qū)用于存放固定數(shù)量的zval,這個(gè)數(shù)量默認(rèn)是10,000,如果需要修改則需要修改源代碼Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES然后重新編譯。
由上文我們可以知道,一個(gè)zval如果有引用,要么被全局符號表中的符號引用,要么被其它表示復(fù)雜類型的zval中的符號引用。因此在zval中存在一些可能根(root)。這里我們暫且不討論P(yáng)HP是如何發(fā)現(xiàn)這些可能根的,這是個(gè)很復(fù)雜的問題,總之PHP有辦法發(fā)現(xiàn)這些可能根zval并將它們投入根緩沖區(qū)。
當(dāng)根緩沖區(qū)滿額時(shí),PHP就會執(zhí)行垃圾回收,此回收算法如下:
1、對每個(gè)根緩沖區(qū)中的根zval按照深度優(yōu)先遍歷算法遍歷所有能遍歷到的zval,并將每個(gè)zval的refcount減1,同時(shí)為了避免對同一zval多次減1(因?yàn)榭赡懿煌母鼙闅v到同一個(gè)zval),每次對某個(gè)zval減1后就對其標(biāo)記為“已減”。
2、再次對每個(gè)緩沖區(qū)中的根zval深度優(yōu)先遍歷,如果某個(gè)zval的refcount不為0,則對其加1,否則保持其為0。
3、清空根緩沖區(qū)中的所有根(注意是把這些zval從緩沖區(qū)中清除而不是銷毀它們),然后銷毀所有refcount為0的zval,并收回其內(nèi)存。
如果不能完全理解也沒有關(guān)系,只需記住PHP5.3的垃圾回收算法有以下幾點(diǎn)特性:
1、并不是每次refcount減少時(shí)都進(jìn)入回收周期,只有根緩沖區(qū)滿額后在開始垃圾回收。
2、可以解決循環(huán)引用問題。
3、可以總將內(nèi)存泄露保持在一個(gè)閾值以下。
PHP5.2與PHP5.3垃圾回收算法的性能比較
PHP Manual中的相關(guān)章節(jié):http://docs.php.net/manual/zh/features.gc.performance-considerations.php
首先是內(nèi)存泄露試驗(yàn),下面直接引用PHP Manual中的實(shí)驗(yàn)代碼和試驗(yàn)結(jié)果圖:
<?phpclass 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";} }?>gc-benchmark.png
可以看到在可能引發(fā)累積性內(nèi)存泄露的場景下,PHP5.2發(fā)生持續(xù)累積性內(nèi)存泄露,而PHP5.3則總能將內(nèi)存泄露控制在一個(gè)閾值以下(與根緩沖區(qū)大小有關(guān))。
與垃圾回收算法相關(guān)的PHP配置
1、可以通過修改php.ini中的zend.enable_gc來打開或關(guān)閉PHP的垃圾回收機(jī)制,也可以通過調(diào)用gc_enable()或gc_disable()打開或關(guān)閉PHP的垃圾回收機(jī)制。
2、在PHP5.3中即使關(guān)閉了垃圾回收機(jī)制,PHP仍然會記錄可能根到根緩沖區(qū),只是當(dāng)根緩沖區(qū)滿額時(shí),PHP不會自動(dòng)運(yùn)行垃圾回收
3、當(dāng)然,任何時(shí)候您都可以通過手工調(diào)用gc_collect_cycles()函數(shù)強(qiáng)制執(zhí)行內(nèi)存回收。
文/codefine(簡書作者)
原文鏈接:http://www.jianshu.com/p/63a381a7f70c
著作權(quán)歸作者所有,轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),并標(biāo)注“簡書作者”。
總結(jié)
以上是生活随笔為你收集整理的PHP内存管理 垃圾回收的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从数据结构角度分析foreach效率比f
- 下一篇: 50个PHP程序性能优化的方法