淺談PHP源碼三十四:PHP5.3新增加的記憶體回收機制(Garbage Collection)

來源:互聯網
上載者:User
這篇文章主要介紹了關於淺談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_BENCHGC_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()] -> [gc_zval_possible_root()]
其中在gc_zval_check_possible_root()函數中,僅對數組和對象執行記憶體回收操作

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變數

以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.