對PHP變量的實現方式以及內存管理的梳理

PHP技術大全 / 2019-03-15 14:56:56

變量

  • 局部變量 PHP中局部變量分配在zend_execute_data結構上,每次執行zend_op_array都會生成一個新的zend_execute_data 局部變量通過編譯時確定的編號進行讀寫操作

靜態變量 靜態變量只會在編譯時初始化,保存在zend_op_array->static_variables 這個哈希表中 靜態變量通過哈希表保存,這就使得能像普通變量那樣有一個固定的編號 編譯時先判斷zend_op_array->static_variables 是否已創建,然后將靜態變量插入哈希表 

    //zend_compile_static_var_common():
if (!CG(active_op_array)->static_variables) {
ALLOC_HASHTABLE(CG(active_op_array)->static_variables);
zend_hash_init(CG(active_op_array)->static_variables, 8, NULL, ZVAL_P
TR_DTOR, 0);
}
//插入靜態變量
zend_hash_update(CG(active_op_array)->static_variables, Z_STR(var_node.u.
constant), value);



例如$count與 static_variables["count"]間的關系如圖所示


  • 垃圾回收 一個是引用計數這個早期就有的基本機制,refcount減到0時,釋放變量 這里同時也介紹下一個比較通用的寫時復制機制,

 $a = 1;     
$b = $a; // 這里變量$a 與變量$b 持有的是同一個zend_val
$a = 2; // 這個時候變量$a的值發生了改變,而顯然,讓$b的值也發生同樣的改變是不符合預期的 所以這個時候就會發生zend_val的復制
//另外一種情況
$a = 1;
$b = &$a; //當$b只有的是對$a的引用時,這兩個變量始終共用同一個zend_val
$a = 2; //這時$b的值也為2

  • 循環引用 
    引用計數機制有一個缺陷,就是碰到循環引用時,refcount無法減到0,導致變量無法釋放,具體來說就是變量內部的成員引用了變量本身,比如數組中的某個元素指向了數組

 $a = [1];     
$a[] = &$a;
unset($a);

針對這種情況,php引入了垃圾回收器來處理 變量是否加入垃圾檢查buffer并不是根據zval的類型判斷的,而是與前面介紹的是否用到引用計數一樣通過 zval.u1.type_flag 記錄的,只有包含 IS_TYPE_COLLECTABLE 的變量才會被GC收集

目前垃圾只會出現在array、object兩種類型中,只有這兩種類型的變量會出現成員引用自身的情況
如果當變量的refcount減少后大于0,PHP并不會立即進行對這個變量進行垃圾鑒定,而是放入一個緩沖buffer中,等這個buffer滿了以后(10000個值)再統一進行處理,加入buffer的是 變量zend_value的 zend_refcounted_h

一個變量只能加入一次buffer,為了防止重復加入,變量加入后會把
zend_refcounted_h.gc_info 置為 GC_PURPLE ,即標為紫色,下次refcount減少時
如果發現已經加入過了則不再重復插入。


垃圾緩存區是一個雙向鏈表,等到緩存區滿了以后則啟動垃圾檢查過程:遍歷緩存區,再對當前變量的所有成員進行遍歷,然后把成員的refcount減1(如果成員還包含子成員則也進行遞歸遍歷,其實就是深度優先的遍歷)
最后再檢查當前變量的引用,如果減為了0則為垃圾
這個算法的原理很簡單,垃圾是由于成員引用自身導致的,那么就對所有的成員減一遍引用,結果如果發現變量本身refcount變為了0則就表明其引用全部來自自身成員。

PHP對象在內存堆棧中的分配

對象在PHP里面和整型、浮點型一樣,也是一種數據類,都是存儲不同類型數據用的, 在運行的時候都要加載到內存中去用,那么對象在內存里面是怎么體現的呢?內存從邏輯上說大體上是分為4段,棧空間段、堆空間段、代碼段、初始化靜態段,程序里面不同的聲明放在不同的內存段里面。

數據段(data segment)通常是指用來存放程序中已初始化且不為0的全局變量如:靜態變量和常量

代碼段(code segment / text segment)通常是指用來存放程序執行代碼的一塊內存區域,比如函數和方法

棧空間段是存儲占用相同空間長度并且占用空間小的數據類型的地方,比如說整型1,10,100,1000,10000,100000 等等,在內存里面占用空間是等長的,都是64 位4 個字節。

(heap)數據長度不定長,而且占有空間很大的數據類型的數據放在堆內存里面的。

棧內存是可以直接存取的,而堆內存是 不可以直接存取的內存。對于我們的對象來數就是一種大的數據類型而且是占用空間不定長的類型,所以說對象是放在堆里面的,但對象名稱是放在棧里面的,這樣通過對象名稱就可 以使用對象了。

  • PHP腳本運行的時候,那些變量被放到了棧內存,那些被保存到了堆內存?

在PHP5的Zend Engine的實現中,所有的值都是在堆上分配空間,并且通過引用計數和垃圾收集來管理. PHP5的Zend Engine主要使用指向zval結構的指針來操作值,在很多地方甚至通過zval的二級指針來操作.

而在PHP7的Zend Engine實現中,值是通過zval結構本身來操作(非指針). 新的zval結構直接被存放在VM[虛擬機?]的棧上,HashTable的桶里,以及屬性槽里. 這樣大大減少了在堆上分配和釋放內存的操作,還避免了對簡單值的引用計數和垃圾收集.

引用:


PHP對象在內存堆棧中的分配 - web21 - 博客園

《PHP7內核剖析》

更多精彩

敬請關注“PHP技術大全”微信公眾號


青海快三开奖信息