php內存管理機制、垃圾回收機制

1、內存管理機制php

先看一段代碼:編程

複製代碼
<?php //內存管理機制
var_dump(memory_get_usage());//獲取內存方法,加上true返回實際內存,不加則返回表現內存
$a = "laruence"; var_dump(memory_get_usage()); unset($a); var_dump(memory_get_usage()); //輸出(在個人我的電腦上, 可能會由於系統,PHP版本,載入的擴展不一樣而不一樣): //int 240552 //int 240720 //int 240552
複製代碼

定義變量以後,內存增長,清除變量以後,內存恢復(有些可能不會恢復和之前同樣),好像定義變量時申請了一次內存,其實不是這樣的,php會預先申請一塊內存,不會每次定義變量就申請內存。數組

首先咱們要打破一個思惟: PHP不像C語言那樣, 只有你顯示的調用內存分配相關API纔會有內存的分配. 也就是說, 在PHP中, 有不少咱們看不到的內存分配過程.
好比對於:
$a = "laruence";
隱式的內存分配點就有:
1.1. 爲變量名分配內存, 存入符號表
2.2. 爲變量值分配內存
因此, 不能只看表象.
第二, 別懷疑,PHP的unset確實會釋放內存, 但這個釋放不是C編程意義上的釋放, 不是交回給OS.
對於PHP來講, 它自身提供了一套和C語言對內存分配類似的內存管理API: 緩存

emalloc(size_t size); efree(void *ptr); ecalloc(size_t nmemb, size_t size); erealloc(void *ptr, size_t size); estrdup(const char *s); estrndup(const char *s, unsigned int length);

這些API和C的API意義對應, 在PHP內部都是經過這些API來管理內存的.
當咱們調用emalloc申請內存的時候, PHP並非簡單的向OS要內存, 而是會像OS要一個大塊的內存, 而後把其中的一塊分配給申請者, 這樣當再有邏輯來申請內存的時候, 就再也不須要向OS申請內存了, 避免了頻繁的系統調用.
好比以下的例子:
post

複製代碼
var_dump(memory_get_usage(true));//注意獲取的是real_size
$a = "laruence"; var_dump(memory_get_usage(true)); unset($a); var_dump(memory_get_usage(true)); //輸出 //int 262144 //int 262144 //int 262144
複製代碼

也就是咱們在定義變量$a的時候, PHP並無向系統申請新內存.一樣的, 在咱們調用efree釋放內存的時候, PHP也不會把內存還給OS, 而會把這塊內存, 納入本身維護的空閒內存列表. 而對於小塊內存來講, 更可能的是, 把它放到內存緩存列表中去(後記, 某些版本的PHP, 好比我驗證過的PHP5.2.4, 5.2.6, 5.2.8, 在調用get_memory_usage()的時候, 不會減去內存緩存列表中的可用內存塊大小, 致使看起來, unset之後內存不變).spa

$a = "hello"; //定義變量時,存儲兩個方面:
//1.變量名,存儲在符號表 //2.變量值存儲在內存空間 //3.在刪除變量的時候,會將變量值存儲的空間釋放,而變量名所在的符號表不會減少(只增不減)

只增不減的數組
Hashtable是PHP的核心結構, 數組也是用她來表示的, 而符號表也是一種關聯數組, 對於以下代碼:debug

複製代碼
var_dump(memory_get_usage()); for($i=0;$i<100;$i++) { $a = "test".$i; $$a = "hello"; } var_dump(memory_get_usage()); for($i=0;$i<100;$i++) { $a = "test".$i; unset($$a); } var_dump(memory_get_usage());
複製代碼

咱們定義了100個變量, 而後又按個Unset了他們, 來看看輸出:code

//int 242104 //int 259768 //int 242920

Wow, 怎麼少了這麼多內存?
這是由於對於Hashtable來講, 定義它的時候, 不可能一次性分配足夠多的內存塊, 來保存未知個數的元素, 因此PHP會在初始化的時候, 只是分配一小部份內存塊給HashTable, 當不夠用的時候再RESIZE擴容。而Hashtable, 只能擴容, 不會減小, 對於上面的例子, 當咱們存入100個變量的時候, 符號表不夠用了, 作了一次擴容, 而當咱們依次unset掉這100個變量之後, 變量佔用的內存是釋放了(118848 – 104448), 可是符號表並無縮小, 因此這些少的內存是被符號表自己佔去了…對象

2、垃圾回收機制blog

PHP變量存儲在一個zval容器裏面的
1.變量類型 2. 變量值 3. is_ref 表明是否有地址引用 4. refcount 指向該值的變量數量

變量賦值的時候:is_ref爲false, refcount爲1

$a = 1; xdebug_debug_zval('a'); echo PHP_EOL;//換行符,提升代碼的源代碼級可移植性

輸出:

a:

(refcount=1, is_ref=0),

int

1

將變量a的值賦給變量b,變量b不會馬上去在內存中存儲值,而是先指向變量a的值,一直到變量a有任何操做的時候

$b = $a; xdebug_debug_zval('a'); echo PHP_EOL;

輸出:

a:

(refcount=2, is_ref=0),

int

1
$c = &$a; xdebug_debug_zval('a'); echo PHP_EOL; xdebug_debug_zval('b'); echo PHP_EOL;

輸出:

a:

(refcount=2, is_ref=1),

int

1

b:

(refcount=1, is_ref=0),

int

1

由於程序又操做了變量a,因此變量b會本身申請一塊內存將值放進去。
因此變量a的zval容器中refcount會減1變爲1,變量c指向a,因此refcount會加1變爲2,is_ref變爲true

垃圾回收
1.在5.2版本或以前版本,PHP會根據refcount值來判斷是否是垃圾
若是refcount值爲0,PHP會當作垃圾釋放掉
這種回收機制有缺陷,對於環狀引用的變量沒法回收

環狀引用:

$attr = array("hello"); $attr[]= &$attr; xdebug_debug_zval('attr'); echo PHP_EOL;

輸出:

attr:

(refcount=2, is_ref=1),
array (size=2)
  0 => (refcount=1, is_ref=0),

string

'hello' (length=5)
1 => (refcount=2, is_ref=1),
    &array

2.在5.3以後版本改進了垃圾回收機制
若是發現一個zval容器中的refcount在增長,說明不是垃圾
若是發現一個zval容器中的refcount在減小,若是減到了0,直接當作垃圾回收
若是發現一個zval容器中的refcount在減小,並無減到0,PHP會把該值放到緩衝區,當作有多是垃圾的懷疑對象
當緩衝區達到臨界值,PHP會自動調用一個方法取遍歷每個值,若是發現是垃圾就清理

相關文章
相關標籤/搜索