PHP中垃圾回收機制詳解
前言
平時很多人說到的gc,就是垃圾回收器,全稱Garbage Collection。
早期版本,準確地說是5.3之前(不包括5.3)的垃圾回收機制,是沒有專門的垃圾回收器的。只是簡單的判斷了一下變數的zval的refcount是否為0,是的話就釋放否則不釋放直至程序結束。
乍一看確實沒毛病啊,然而其中隱藏著變數記憶體溢位的風險:http://bugs.php.net/bug.php?id=33595 ,無法回收的記憶體造成了記憶體洩漏,所以PHP5.3出現了專門負責清理垃圾資料、防止記憶體洩漏的GC。
php引用計數基本知識點
不準確但卻通俗的說:
refcount:多少個變數是一樣的用了相同的值,這個數值就是多少。
is_ref:bool型別,當refcount大於2的時候,其中一個變數用了地址&的形式進行賦值,好了,它就變成1了。
主要講講如何用php來直觀的看到這些計數的變化,走一波。
首先需要在php上裝上xdebug的擴充套件。
字串型別變數
1.檢視內部結構
<?php
$name = "咖啡色的羊駝";
xdebug_debug_zval('name');
會得到:
name:(refcount=1, is_ref=0),string '咖啡色的羊駝' (length=18)
2.增加一個計數
<?php
$name = "咖啡色的羊駝";
$temp_name = $name;
xdebug_debug_zval('name');
會得到:
name:(refcount=2, is_ref=0),string '咖啡色的羊駝' (length=18)
看到了吧,refcount+1了。
3.引用賦值
<?php
$name = "咖啡色的羊駝";
$temp_name = &$name;
xdebug_debug_zval('name');
會得到:
name:(refcount=2, is_ref=1),string '咖啡色的羊駝' (length=18)
是的引用賦值會導致zval通過is_ref來標記是否存在引用的情況。
陣列型的變數
<?php
$name = ['a'=>'咖啡色', 'b'=>'的羊駝'];
xdebug_debug_zval('name');
會得到:
name:
(refcount=1, is_ref=0),
array (size=2)
'a' => (refcount=1, is_ref=0),string '咖啡色' (length=9)
'b' => (refcount=1, is_ref=0),string '的羊駝' (length=9)
還挺好理解的,對於陣列來看是一個整體,對於內部kv來看又是分別獨立的整體,各自都維護著一套zval的refount和is_ref。
銷燬變數
<?php
$name = "咖啡色的羊駝";
$temp_name = $name;
xdebug_debug_zval('name');
unset($temp_name);
xdebug_debug_zval('name');
會得到:
name:(refcount=2, is_ref=0),string '咖啡色的羊駝' (length=18)
name:(refcount=1, is_ref=0),string '咖啡色的羊駝' (length=18)
refcount計數減1,說明unset並非一定會釋放記憶體,當有兩個變數指向的時候,並非會釋放變數佔用的記憶體,只是refcount減1.
php的記憶體管理機制
知道了zval是怎麼一回事,接下來看看如何通過php直觀看到記憶體管理的機制是怎麼樣的。
外在的記憶體變化
<?php
//獲取記憶體方法,加上true返回實際記憶體,不加則返回表現記憶體
var_dump(memory_get_usage());
$name = "咖啡色的羊駝";
var_dump(memory_get_usage());
unset($name);
var_dump(memory_get_usage());
會得到:
int 1593248
int 1593384
int 1593248
大致過程:定義變數->記憶體增加->清除變數->記憶體恢復
潛在的記憶體變化
當執行 時候,記憶體的分配做了兩件事情:
1.為變數名分配記憶體,存入符號表
2.為變數值分配記憶體
$name = "咖啡色的羊駝";
再來看程式碼:
<?php
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());
會得到:
int 1596864
int 1612080
int 1597680
簡直爆炸,怎麼和之前看的不一樣?記憶體沒有全部回收回來。
對於php的核心結構Hashtable來說,由於未知性,定義的時候不可能一次性分配足夠多的記憶體塊。所以初始化的時候只會分配一小塊,等不夠的時候在進行擴容,而Hashtable只擴容不減少,所以就出現了上述的情況:當存入100個變數的時候,符號表不夠用了就進行一次擴容,當unset的時候只釋放了”為變數值分配記憶體”,而“為變數名分配記憶體”是在符號表的,符號表並沒有縮小,所以沒收回來的記憶體是被符號表佔去了。
潛在的記憶體申請與釋放設計
php和c語言一樣,也是需要進行申請記憶體的,只不過這些操作作者都封裝到底層了,php使用者無感知而已。
php並非簡單的向os申請記憶體,而是會申請一大塊記憶體,把其中一部分分給申請者,這樣當再有邏輯來申請記憶體的時候,就不需要向os申請了,避免了頻繁呼叫。當記憶體不夠的時候才會再次申請
當釋放記憶體的時候,php並非會把記憶體還給os,而是把記憶體軌道自己維護的空閒記憶體列表,以便重複利用。
以上內容希望幫助到大家, 很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務程式碼寫多了沒有方向感,不知道該從那裡入手去提升,對此我整理了一些資料,包括但不限於:分散式架構、高可擴充套件、高效能、高併發、伺服器效能調優、TP6,laravel,Redis,Swoole、Swoft、Kafka、Mysql優化、shell指令碼、Docker、微服務、Nginx等多個知識點高階進階乾貨需要的可以免費分享給大家 ,需要戳這裡 PHP進階架構師>>>實戰視訊、大廠面試文件免費獲取
- 基於Nginx的負載均衡原理與實戰
- PHP控制反轉(IOC)和依賴注入(DI)
- 深入理解PHP7核心之Reference
- php中類的不定引數使用示例
- php單例模式的常見應用場景
- laravel 配置MySQL讀寫分離
- PHP的垃圾回收機制(建議收藏)
- 【shell指令碼】字串和陣列的使用
- PHP-FPM是什麼東東?
- PHP 編寫守護程序
- PHP命令列指令碼接收傳入引數的三種方式
- php專案中類的自動載入
- 複習下Linux去除重複項命令uniq
- 深入理解PHP核心:變數及資料型別
- Swoole協程與傳統fpm同步模式比較
- PHP中Session ID的實現原理
- 寫一手好SQL,該從哪裡入手最好?
- PHP命令列指令碼接收傳入引數的三種方式
- 使用 Shell 在多伺服器上批量操作
- PHP實現使用者異地登入提醒功能的方法