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

1、內存管理機制

先看一段代碼:php

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

 

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

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

1 emalloc(size_t size);
2 efree(void *ptr);
3 ecalloc(size_t nmemb, size_t size);
4 erealloc(void *ptr, size_t size);
5 estrdup(const char *s);
6 estrndup(const char *s, unsigned int length);

 

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

好比如下的例子:app

1 var_dump(memory_get_usage(true));//注意獲取的是real_size
2 $a = "laruence";
3 var_dump(memory_get_usage(true));
4 unset($a);
5 var_dump(memory_get_usage(true));
6 //輸出
7 //int 262144
8 //int 262144
9 //int 262144

 

也就是咱們在定義變量$a的時候, PHP並無向系統申請新內存.一樣的, 在咱們調用efree釋放內存的時候, PHP也不會把內存還給OS, 而會把這塊內存, 納入本身維護的空閒內存列表. 而對於小塊內存來講, 更可能的是, 把它放到內存緩存列表中去spa

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

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

 1 var_dump(memory_get_usage());
 2 for($i=0;$i<100;$i++)
 3 {
 4     $a = "test".$i;
 5     $$a = "hello";    
 6 }
 7 var_dump(memory_get_usage());
 8 for($i=0;$i<100;$i++)
 9 {
10     $a = "test".$i;
11     unset($$a);    
12 }
13 var_dump(memory_get_usage());

 

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

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

 

怎麼少了這麼多內存?
這是由於對於Hashtable來講, 定義它的時候, 不可能一次性分配足夠多的內存塊, 來保存未知個數的元素, 因此PHP會在初始化的時候, 只是分配一小部份內存塊給HashTable, 當不夠用的時候再RESIZE擴容。而Hashtable, 只能擴容, 不會減小,對象

對於上面的例子, 當咱們存入100個變量的時候, 符號表不夠用了, 作了一次擴容, 而當咱們依次unset掉這100個變量之後, 變量佔用的內存是釋放了(118848 – 104448), 可是符號表並無縮小, 因此這些少的內存是被符號表自己佔去了…blog

 

2、垃圾回收機制

PHP變量存儲在一個zval容器裏面的
1.變量類型

2. 變量值

3. is_ref 表明是否有地址引用

4. refcount 指向該值的變量數量

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

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

 

輸出:

a:
1 (refcount=1, is_ref=0),
2 int 1

 

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

1 $b = $a;
2 xdebug_debug_zval('a');
3 echo PHP_EOL;

 

輸出:

 1 a:
 2 
 3 (refcount=2, is_ref=0),
 4 int
 5 
 6  1
 7 
 8 $c = &$a;
 9 xdebug_debug_zval('a');
10 echo PHP_EOL;
11 
12 xdebug_debug_zval('b');
13 echo PHP_EOL;

 

輸出:

 1 a:
 2 
 3 (refcount=2, is_ref=1),
 4 int
 5 
 6  1
 7 b:
 8 
 9 (refcount=1, is_ref=0),
10 int
11 
12  1

 

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

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

環狀引用:

1 $attr = array("hello");
2 $attr[]= &$attr;
3 
4 xdebug_debug_zval('attr');
5 echo PHP_EOL;

 

輸出:

 1 attr:
 2 
 3 (refcount=2, is_ref=1),
 4 array (size=2)
 5   0 => (refcount=1, is_ref=0),
 6 string
 7 
 8  'hello' (length=5)
 9   1 => (refcount=2, is_ref=1),
10     &array

 

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

相關文章
相關標籤/搜索