【PHP7源碼學習】2019-04-02 PHP垃圾回收2

baiyan算法

所有視頻:https://segmentfault.com/a/11...segmentfault

垃圾回收器緩衝區元素的存儲

  • 在上一篇文章【PHP源碼學習】2019-04-01 PHP垃圾回收1中,咱們將全部疑似垃圾的元素都放到垃圾回收器緩衝區中,一直存下去,待存滿垃圾回收器緩衝區10000個存儲單元以後,垃圾回收算法就會啓動,對緩衝區中的全部疑似垃圾進行標記與清除。垃圾回收算法須要對這個緩衝區進行掃描遍歷,斷定每個存儲單元中的內容是否爲最終的垃圾。
  • 回顧gc_possible_root()函數,咱們將疑似垃圾存入了一個大小爲10000的緩衝區中:
ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
{
    ...
    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)) {
            return;
        }
        GC_REFCOUNT(ref)++;
        gc_collect_cycles(); //真正啓動垃圾回收算法
        GC_REFCOUNT(ref)--;
        ...
    }
}
  • 因而可知,垃圾回收算法會在緩衝區存滿的時候纔會啓動,這樣會減小垃圾回收算法運行的頻率,進而減少性能的損耗且提升效率(詳見上一篇文章)

垃圾回收算法的運行

  • 在此以前,咱們已經將垃圾緩衝區內的全部元素均標記成了紫色GC_PURPLE
  • 咱們在gc_possible_root()函數中調用了gc_collect_cycles()函數,這個gc_collect_cycles是一個函數指針,實際指向的是zend_gc_collect_cycles()函數,最終會調用它:
ZEND_API int zend_gc_collect_cycles(void)
{
        
        ...
        //遍歷垃圾回收器鏈表,若是垃圾回收器鏈表中的元素是紫色,對其進行深度優先遍歷並將refcount減1,並把它們標記爲灰色GC_GREY
        gc_mark_roots();
    
        //遍歷垃圾回收器鏈表,若是垃圾回收器鏈表中的元素是灰色,那麼判斷每一個疑似垃圾的元素的refcount是否>0,若是>0就不是垃圾,將其標記爲黑色GC_BLACK而且將refcount+1恢復到原來的值;不然爲垃圾,將其標記爲白色GC_WHITE,它們是真正的垃圾,後面須要釋放掉
        gc_scan_roots();
        ...
        
        //把垃圾回收器中爲白色GC_WHITE的垃圾單獨摘出來放到to_free垃圾鏈表中,並刪除垃圾回收器中不是垃圾的元素
        count = gc_collect_roots(&gc_flags);

        GC_G(gc_active) = 0;

        if (GC_G(to_free).next == &GC_G(to_free)) {
            /* 若是垃圾鏈表to_free爲空,那麼就沒有須要釋放的垃圾,直接返回 */
            GC_TRACE("Nothing to free");
            return 0;
        }

        /* 將全局變量中的垃圾鏈表拷貝到局部變量中 */
        to_free.next = GC_G(to_free).next;
        to_free.prev = GC_G(to_free).prev;
        to_free.next->prev = &to_free;
        to_free.prev->next = &to_free;

        /* 釋放全局的垃圾鏈表 */
        GC_G(to_free).next = &GC_G(to_free);
        GC_G(to_free).prev = &GC_G(to_free);

        orig_next_to_free = GC_G(next_to_free);

        ...

        if (gc_flags & GC_HAS_DESTRUCTORS) {
            GC_TRACE("Calling destructors");

            /* 在析構以前記錄引用計數*/
            current = to_free.next;
            while (current != &to_free) {
                current->refcount = GC_REFCOUNT(current->ref);
                current = current->next;
            }

            /* 析構對象*/
            current = to_free.next;
            while (current != &to_free) {
                p = current->ref;
                GC_G(next_to_free) = current->next;
                if (GC_TYPE(p) == IS_OBJECT) {
                    zend_object *obj = (zend_object*)p;

                    if (!(GC_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) {
                        GC_TRACE_REF(obj, "calling destructor");
                        GC_FLAGS(obj) |= IS_OBJ_DESTRUCTOR_CALLED;
                        if (obj->handlers->dtor_obj
                         && (obj->handlers->dtor_obj != zend_objects_destroy_object
                          || obj->ce->destructor)) {
                            GC_REFCOUNT(obj)++;
                            obj->handlers->dtor_obj(obj);
                            GC_REFCOUNT(obj)--;
                        }
                    }
                }
                current = GC_G(next_to_free);
            }

            /* 銷燬在對象析構過程當中出現的值 */
            current = to_free.next;
            while (current != &to_free) {
                GC_G(next_to_free) = current->next;
                if (GC_REFCOUNT(current->ref) > current->refcount) {
                    gc_remove_nested_data_from_buffer(current->ref, current);
                }
                current = GC_G(next_to_free);
            }
        }

        /* 銷燬zval */
        GC_TRACE("Destroying zvals");
        GC_G(gc_active) = 1;
        current = to_free.next;
        while (current != &to_free) {
            p = current->ref;
            GC_G(next_to_free) = current->next;
            GC_TRACE_REF(p, "destroying");
            if (GC_TYPE(p) == IS_OBJECT) { //釋放對象
                zend_object *obj = (zend_object*)p;

                EG(objects_store).object_buckets[obj->handle] = SET_OBJ_INVALID(obj);
                GC_TYPE(obj) = IS_NULL;
                if (!(GC_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {
                    GC_FLAGS(obj) |= IS_OBJ_FREE_CALLED;
                    if (obj->handlers->free_obj) {
                        GC_REFCOUNT(obj)++;
                        obj->handlers->free_obj(obj);
                        GC_REFCOUNT(obj)--;
                    }
                }
                SET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[obj->handle], EG(objects_store).free_list_head);
                EG(objects_store).free_list_head = obj->handle;
                p = current->ref = (zend_refcounted*)(((char*)obj) - obj->handlers->offset);
            } else if (GC_TYPE(p) == IS_ARRAY) { //釋放數組
                zend_array *arr = (zend_array*)p;

                GC_TYPE(arr) = IS_NULL;

                /* GC may destroy arrays with rc>1. This is valid and safe. */
                HT_ALLOW_COW_VIOLATION(arr);

                zend_hash_destroy(arr);
            }
            current = GC_G(next_to_free);
        }

        /* 釋放垃圾所佔用內存空間 */
        current = to_free.next;
        while (current != &to_free) {
            next = current->next;
            p = current->ref;
            if (EXPECTED(current >= GC_G(buf) && current < GC_G(buf) + GC_ROOT_BUFFER_MAX_ENTRIES)) {
                current->prev = GC_G(unused);
                GC_G(unused) = current;
            }
            efree(p);
            current = next;
        }

        while (GC_G(additional_buffer) != additional_buffer_snapshot) {
            gc_additional_buffer *next = GC_G(additional_buffer)->next;
            efree(GC_G(additional_buffer)); //經過efree()釋放內存空間
            GC_G(additional_buffer) = next;
        }

        /* 收尾 */
        GC_TRACE("Collection finished");
        GC_G(collected) += count;
        GC_G(next_to_free) = orig_next_to_free;
#if ZEND_GC_DEBUG
        GC_G(gc_full) = orig_gc_full;
#endif
        GC_G(gc_active) = 0;
    }

    return count;
}
  • 在第一步的gc_mark_roots()中,調用了gc_mark_grey(),標記爲灰色:
static void gc_mark_roots(void)
{
    gc_root_buffer *current = GC_G(roots).next;

    while (current != &GC_G(roots)) {
        if (GC_REF_GET_COLOR(current->ref) == GC_PURPLE) {
            gc_mark_grey(current->ref);
        }
        current = current->next;
    }
}
  • 在第二步的gc_scan_roots()中,調用了gc_scan(),進而又調用了gc_mark_black(),標記爲黑色,而標記爲白色沒有調用函數:
static void gc_scan_roots(void)
{
    gc_root_buffer *current = GC_G(roots).next;

    while (current != &GC_G(roots)) {
        gc_scan(current->ref); //內部還會調用gc_mark_black()
        current = current->next;
    }
}
  • 下面咱們總結一下垃圾回收算法的執行流程:
- 調用函數gc_mark_roots();首先對垃圾回收器進行深度優先遍歷,將refcount-1,並標記爲灰色
 - 調用函數gc_scan_roots();再次對垃圾回收器進行深度優先遍歷,判斷當前的refcount,若是大於0,就不是垃圾,標記爲黑色,而且要將refcount+1恢復到原來的值;若是等於0,就是垃圾,標記爲白色
 - 調用函數gc_collect_roots(),將白色的垃圾從垃圾回收器中摘出來,放到垃圾鏈表中並等待回收;同時將垃圾回收器中不是垃圾的元素移除,以節省垃圾回收器空間
 - 釋放全局垃圾鏈表,拷貝到局部垃圾鏈表,提升效率
 - 遍歷局部垃圾鏈表
 - 首先析構對象
 - 銷燬在對象析構過程當中出現的值
 - 銷燬對象與數組的zval
 - 釋放垃圾所佔用內存空間
 - 收尾工做
  • 那麼對其進行深度優先遍歷並標記的代碼都相似,舉一個標記灰色的例子:
static void gc_mark_grey(zend_refcounted *ref)
{
    HashTable *ht;
    Bucket *p, *end;
    zval *zv;

tail_call:
    if (GC_REF_GET_COLOR(ref) != GC_GREY) {
        ht = NULL;
        GC_BENCH_INC(zval_marked_grey);
        GC_REF_SET_COLOR(ref, GC_GREY);

        if (GC_TYPE(ref) == IS_OBJECT) {
            zend_object_get_gc_t get_gc;
            zend_object *obj = (zend_object*)ref;

            if (EXPECTED(!(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED) &&
                         (get_gc = obj->handlers->get_gc) != NULL)) {
                int n;
                zval *zv, *end;
                zval tmp;

                ZVAL_OBJ(&tmp, obj);
                ht = get_gc(&tmp, &zv, &n);
                end = zv + n;
                if (EXPECTED(!ht)) {
                    if (!n) return;
                    while (!Z_REFCOUNTED_P(--end)) {
                        if (zv == end) return;
                    }
                }
                while (zv != end) {
                    if (Z_REFCOUNTED_P(zv)) {
                        ref = Z_COUNTED_P(zv);
                        GC_REFCOUNT(ref)--;
                        gc_mark_grey(ref);
                    }
                    zv++;
                }
                if (EXPECTED(!ht)) {
                    ref = Z_COUNTED_P(zv);
                    GC_REFCOUNT(ref)--;
                    goto tail_call;
                }
            } else {
                return;
            }
        } else if (GC_TYPE(ref) == IS_ARRAY) {
            if (((zend_array*)ref) == &EG(symbol_table)) {
                GC_REF_SET_BLACK(ref);
                return;
            } else {
                ht = (zend_array*)ref;
            }
        } else if (GC_TYPE(ref) == IS_REFERENCE) {
            if (Z_REFCOUNTED(((zend_reference*)ref)->val)) {
                ref = Z_COUNTED(((zend_reference*)ref)->val);
                GC_REFCOUNT(ref)--;
                goto tail_call;
            }
            return;
        } else {
            return;
        }

        if (!ht->nNumUsed) return;
        p = ht->arData;
        end = p + ht->nNumUsed;
        while (1) {
            end--;
            zv = &end->val;
            if (Z_TYPE_P(zv) == IS_INDIRECT) {
                zv = Z_INDIRECT_P(zv);
            }
            if (Z_REFCOUNTED_P(zv)) {
                break;
            }
            if (p == end) return;
        }
        while (p != end) {
            zv = &p->val;
            if (Z_TYPE_P(zv) == IS_INDIRECT) {
                zv = Z_INDIRECT_P(zv);
            }
            if (Z_REFCOUNTED_P(zv)) {
                ref = Z_COUNTED_P(zv);
                GC_REFCOUNT(ref)--;
                gc_mark_grey(ref);
            }
            p++;
        }
        zv = &p->val;
        if (Z_TYPE_P(zv) == IS_INDIRECT) {
            zv = Z_INDIRECT_P(zv);
        }
        ref = Z_COUNTED_P(zv);
        GC_REFCOUNT(ref)--;
        goto tail_call;
    }
}
  • 咱們能夠看到,該函數中用到了大量的遞歸調用。咱們知道,垃圾回收就是用來解決循環引用的問題。循環引用的問題是本身引用本身,那麼究竟在哪裏才引用了本身,就須要進行深度優先遍歷。舉個例子,數組中的元素可能又是一個數組,裏面的數組元素可能仍是一個數組,間接引用了好多層,最後一層才引用了外層數組自己。那麼,這種狀況下,就須要不斷地對其進行遍歷並標記,直到找到最終最深的引用位置以前,全部中間的間接引用層都是垃圾,咱們都須要對其進行標記而且清除。
  • 好比以前講過的循環引用示例:

  • 這個例子很簡單,只是嵌套了一層就引用回了本身。若是bucket中又是一個數組,它就會再次指向一個zend_array,而這個新zend_array又指向了一個bucket......等等,咱們能夠無限層嵌套下去,直到最後一層,纔是深度優先遍歷的終點。因此,咱們在這個過程當中所遍歷的zend_array與bucket,所有都是垃圾,須要咱們去標記與清除。
  • 觀察上圖,咱們能夠注意到,在這個遍歷過程當中,全部的結構(zend_array或bucket等)的refcount都爲1,這就證實了在垃圾回收算法中,爲何要先將refcount-1,而後判斷是否大於0,若是小於等於0,那麼他們就是垃圾,須要回收;若是大於0,則說明還有其餘地方引用這個結構,那麼他們不是垃圾,不須要回收。
  • 咱們最後總結一下顏色的變化:紫色 -> 灰色 -> 黑色或白色
- 紫色:表明該元素已經放入垃圾回收器
- 灰色:表明該元素已經通過了refcount-1的操做
- 黑色:不是垃圾,要將refcount+1還原
- 白色:是垃圾,等待後續回收
相關文章
相關標籤/搜索