首頁  >  文章  >  後端開發  >  淺談PHP原始碼三十四:PHP5.3新增加的垃圾回收機制(Garbage Collection)

淺談PHP原始碼三十四:PHP5.3新增加的垃圾回收機制(Garbage Collection)

不言
不言原創
2018-06-29 10:07:011028瀏覽

這篇文章主要介紹了關於淺談PHP源碼三十四:PHP5.3新增加的垃圾回收機制(Garbage Collection),有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

淺談PHP原始碼三十四:PHP5.3新增加的垃圾回收機制(Garbage Collection)
在之前的文章淺談PHP源碼三十三:PHP5.3新增加的垃圾回收機制(Garbage Collection)基礎 中有介紹了垃圾回收機制的一些基礎。今天我們來看看其初始化,添加到垃圾緩衝區和垃圾回收的過程。
官方說明文件請猛擊Garbage Collection
中文版位址:http://docs.php.net/manual/zh/features.gc.php
【初始化】
在zend/zend_gc .c 121行有函數gc_init實作了gc的初始化,其程式碼如下:

 ZEND_API void gc_init(TSRMLS_D){
if (GC_G(buf) == NULL && GC_G(gc_enabled)) {
GC_G(buf) = (gc_root_buffer*) malloc(sizeof(gc_root_buffer) * GC_ROOT_BUFFER_MAX_ENTRIES);
GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
gc_reset(TSRMLS_C);
}}

第123行判斷是否為空和是否開啟了gc,如果都為真,則轉124行
第124行是直接呼叫malloc分配了10000個gc_root_buffer記憶體。
第125行將全域變數last_unused設定為gc緩衝區的結束位置。
第126行重置整個垃圾收集機制,其程式碼從zend/zend_gc.c 88行開始,如下:

ZEND_API void gc_reset(TSRMLS_D){
GC_G(gc_runs) = 0;
GC_G(collected) = 0; #if GC_BENCH
GC_G(root_buf_length) = 0;
GC_G(root_buf_peak) = 0;
GC_G(zval_possible_root) = 0;
GC_G(zobj_possible_root) = 0;
GC_G(zval_buffered) = 0;
GC_G(zobj_buffered) = 0;
GC_G(zval_remove_from_buffer) = 0;
GC_G(zobj_remove_from_buffer) = 0;
GC_G(zval_marked_grey) = 0;
GC_G(zobj_marked_grey) = 0;#endif 
GC_G(roots).next = &GC_G(roots);
GC_G(roots).prev = &GC_G(roots); if (GC_G(buf)) {
GC_G(unused) = NULL;
GC_G(first_unused) = GC_G(buf); 
GC_G(zval_to_free) = NULL;
} else {
GC_G(unused) = NULL;
GC_G(first_unused) = NULL;
GC_G(last_unused) = NULL;
}}

第90~91行設定gc運行的次數統計(gc_runs)和gc中垃圾的個數(collected)為0。
第106~107行 設定雙向鍊錶頭結點的上一個結點和下一個結點指向自己。

關於gc_enabled,預設是開啟的,可以在php.ini配置。
其實作程式碼在zend/zend.c 93行如下:

STD_ZEND_INI_BOOLEAN("zend.enable_gc","1",ZEND_INI_ALL,OnUpdateGCEnabled,   gc_enabled, zend_gc_globals,        gc_globals)

初始化呼叫在zend/zend.c 79 行

 static ZEND_INI_MH(OnUpdateGCEnabled) /* {{{ */{
OnUpdateBool(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC); if (GC_G(gc_enabled)) {
gc_init(TSRMLS_C);
} return SUCCESS;}

【新增到垃圾緩衝區】
追蹤PHP的原始碼 zend/zend_execute_API.c 424行
[_zval_ptr_dtor] -> [GC_ZVAL_CHECK_POSSIBLE_ROOT()] -> [gc_zval_check_possible_root()] -root&Fs_cal;函數中,僅對數組和物件執行垃圾回收操作

gc_zval_possible_root函數的程式碼如下:

ZEND_API void gc_zval_possible_root(zval *zv TSRMLS_DC){
if (UNEXPECTED(GC_G(free_list) != NULL &&
               GC_ZVAL_ADDRESS(zv) != NULL &&
           GC_ZVAL_GET_COLOR(zv) == GC_BLACK) &&
           (GC_ZVAL_ADDRESS(zv) < GC_G(buf) ||
            GC_ZVAL_ADDRESS(zv) >= GC_G(last_unused))) {
/* The given zval is a garbage that is going to be deleted by
 * currently running GC */
return;
} if (zv->type == IS_OBJECT) {
GC_ZOBJ_CHECK_POSSIBLE_ROOT(zv);
return;
} 
GC_BENCH_INC(zval_possible_root); if (GC_ZVAL_GET_COLOR(zv) != GC_PURPLE) {
GC_ZVAL_SET_PURPLE(zv); if (!GC_ZVAL_ADDRESS(zv)) {
gc_root_buffer *newRoot = GC_G(unused); if (newRoot) {
GC_G(unused) = newRoot->prev;
} else if (GC_G(first_unused) != GC_G(last_unused)) {
newRoot = GC_G(first_unused);
GC_G(first_unused)++;
} else {
if (!GC_G(gc_enabled)) {
GC_ZVAL_SET_BLACK(zv);
return;
}
zv->refcount__gc++;
gc_collect_cycles(TSRMLS_C);
zv->refcount__gc--;
newRoot = GC_G(unused);
if (!newRoot) {
return;
}
GC_ZVAL_SET_PURPLE(zv);
GC_G(unused) = newRoot->prev;
} 
newRoot->next = GC_G(roots).next;
newRoot->prev = &GC_G(roots);
GC_G(roots).next->prev = newRoot;
GC_G(roots).next = newRoot; 
GC_ZVAL_SET_ADDRESS(zv, newRoot); 
newRoot->handle = 0;
newRoot->u.pz = zv; 
GC_BENCH_INC(zval_buffered);
GC_BENCH_INC(root_buf_length);
GC_BENCH_PEAK(root_buf_peak, root_buf_length);
}
}}

第132~140行檢查zval結點資訊是否已放入到結點緩衝區,如果已經放入到結點緩衝區,則直接傳回,這樣可以最佳化其效能

第142~145行處理物件結點,直接傳回,不再執行後面的操作

第149行判斷結點是否已經被標記為紫色,如果為紫色則不再添加到結點緩衝區,此處在於保證一個結點只執行一次添加到緩衝區的操作。

第150行將結點的顏色標記為紫色,表示此結點已經加入緩衝區,下次不用再做添加

第153~157行找出新的結點的位置,如果緩衝區滿了,則執行垃圾回收操作。

第176~184行 將新的結點加入緩衝區所在的雙向鍊錶。

【垃圾回收過程】

在gc_zval_possible_root函數中,當緩衝區滿時,程式呼叫gc_collect_cycles函數,執行垃圾回收作業。從zend/zend_gc.c檔615行開始,其實作程式碼如下:

 ZEND_API int gc_collect_cycles(TSRMLS_D){
int count = 0; if (GC_G(roots).next != &GC_G(roots)) {
zval_gc_info *p, *q, *orig_free_list, *orig_next_to_free; if (GC_G(gc_active)) {
return 0;
}
GC_G(gc_runs)++;
GC_G(zval_to_free) = FREE_LIST_END;
GC_G(gc_active) = 1;
gc_mark_roots(TSRMLS_C);
gc_scan_roots(TSRMLS_C);
gc_collect_roots(TSRMLS_C); 
orig_free_list = GC_G(free_list);
orig_next_to_free = GC_G(next_to_free);
p = GC_G(free_list) = GC_G(zval_to_free);
GC_G(zval_to_free) = NULL;
GC_G(gc_active) = 0; /* First call destructors */
while (p != FREE_LIST_END) {
if (Z_TYPE(p->z) == IS_OBJECT) {
if (EG(objects_store).object_buckets &&
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].valid &&
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount <= 0 &&
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.dtor &&
!EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].destructor_called) { 
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].destructor_called = 1;
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount++;
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.dtor(EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.object, Z_OBJ_HANDLE(p->z) TSRMLS_CC);
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount--;
}
}
count++;
p = p->u.next;
} /* Destroy zvals */
p = GC_G(free_list);
while (p != FREE_LIST_END) {
GC_G(next_to_free) = p->u.next;
if (Z_TYPE(p->z) == IS_OBJECT) {
if (EG(objects_store).object_buckets &&
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].valid &&
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount <= 0) {
EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount = 1;
Z_TYPE(p->z) = IS_NULL;
zend_objects_store_del_ref_by_handle_ex(Z_OBJ_HANDLE(p->z), Z_OBJ_HT(p->z) TSRMLS_CC);
}
} else if (Z_TYPE(p->z) == IS_ARRAY) {
Z_TYPE(p->z) = IS_NULL;
zend_hash_destroy(Z_ARRVAL(p->z));
FREE_HASHTABLE(Z_ARRVAL(p->z));
} else {
zval_dtor(&p->z);
Z_TYPE(p->z) = IS_NULL;
}
p = GC_G(next_to_free);
} /* Free zvals */
p = GC_G(free_list);
while (p != FREE_LIST_END) {
q = p->u.next;
FREE_ZVAL_EX(&p->z);
p = q;
}
GC_G(collected) += count;
GC_G(free_list) = orig_free_list;
GC_G(next_to_free) = orig_next_to_free;
} return count;}

第619行判斷緩衝區是否為空,如果為空則不會執行垃圾回收作業

第622行判斷垃圾回收操作是否正則進行,如果正在進行,則直接返回
第625~627行將垃圾回收操作次數加1,初始化空閒列表,設置gc_active為1表示垃圾回歸正在進行
第628行此處為其官方文件中演算法的步驟B ,演算法使用深度優先搜尋查找所有可能的根,找到後將每個變數容器中的引用計數減1″,為確保不會對同一個變數容器減兩次」1 ″,用灰色標記已減過1的。
第629行這是演算法的步驟C ,演算法再一次對每個根節點使用深度優先搜索,檢查每個變數容器的引用計數。如果引用計數是0 ,變數容器用白色來標記。如果引用次數大於0,則恢復在這個點上使用深度優先搜尋而將引用計數減1的操作(即引用計數加1),然後將它們重新用黑色標記。
第630行演算法的最後一步D ,演算法遍歷根緩衝區以從那裡刪除變數容器根(zval roots),同時,檢查是否有在上一步中被白色標記的變數容器。每個被白色標記的變數容器都會清除。
在[gc_collect_cycles() -> gc_collect_roots() -> zval_collect_white() ]中我們可以看到,對於白色標記的結點會被加入到全域變數zval_to_free清單中。此列表在後面的操作中有用到。
第632~633行將全域變數free_list和next_to_free存放在相對應當的臨時變數中,在最後會恢復到此時的狀態。
第634~635行初始化需要清除的列表,清空將要清空的zval列表並且將垃圾收集的操作狀態為不激活狀態。
第639~655行第一次調用析構函數,並統計清除的變數個數
第657~678行清除變數
第682~686行釋放記憶體
第687~689行處理垃圾個數統計,恢復free_list和next_to_free變數

以上就是本文的全部內容,希望對大家的學習有幫助,更多相關內容請關注PHP中文網!

相關推薦:

淺談PHP源碼三十三:PHP5.3新增加的垃圾回收機制(Garbage Collection)基礎

淺談PHP源碼三十二:PHP記憶體池中的emalloc/efree層與堆(heap)層

#淺聊PHP源碼二十九:關於介面的繼承

#

以上是淺談PHP原始碼三十四:PHP5.3新增加的垃圾回收機制(Garbage Collection)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn