<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                如前面所說,在PHP中,主要的內存管理手段是引用計數,引入垃圾收集機制的目的是為了打破引用計數中的循環引用,從而防止因為這個而產生的內存泄露。垃圾收集機制基于PHP的動態內存管理而存在。PHP5.3為引入垃圾收集機制,在變量存儲的基本結構上有一些變動,如下所示: struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; }; 與PHP5.3之前的版本相比,引用計數字段refcount和是否引用字段is_ref都在其后面添加了__gc以用于新的的垃圾回收機制。在PHP的源碼風格中,大量的宏是一個非常鮮明的特點。這些宏相當于一個接口層,它屏蔽了接口層以下的一些底層實現,如,ALLOC_ZVAL宏,這個宏在PHP5.3之前是直接調用PHP的內存管理分配函數emalloc分配內存,所分配的內存大小由變量的類型等大小決定。在引入垃圾回收機制后,ALLOC_ZVAL宏直接采用新的垃圾回收單元結構,所分配的大小都是一樣的,全部是zval_gc_info結構體所占內存大小,并且在分配內存后,初始化這個結構體的垃圾回收機制。如下代碼: /* The following macroses override macroses from zend_alloc.h */ #undef ALLOC_ZVAL #define ALLOC_ZVAL(z) \ do { \ (z) = (zval*)emalloc(sizeof(zval_gc_info)); \ GC_ZVAL_INIT(z); \ } while (0) zend_gc.h文件在zend.h的749行被引用:#include “zend_gc.h”從而替換覆蓋了在237行引用的zend_alloc.h文件中的ALLOC_ZVAL等宏在新的的宏中,關鍵性的改變是對所分配內存大小和分配內容的改變,在以前純粹的內存分配中添加了垃圾收集機制的內容,所有的內容都包括在zval_gc_info結構體中: typedef struct _zval_gc_info { zval z; union { gc_root_buffer *buffered; struct _zval_gc_info *next; } u; } zval_gc_info; 對于任何一個ZVAL容器存儲的變量,分配了一個zval結構,這個結構確保其和以zval變量分配的內存的開始對齊,從而在zval_gc_info類型指針的強制轉換時,其可以作為zval使用。在zval字段后面有一個聯合體:u。u包括gc_root_buffer結構的buffered字段和zval_gc_info結構的next字段。這兩個字段一個是表示垃圾收集機制緩存的根結點,一個是zval_gc_info列表的下一個結點,垃圾收集機制緩存的結點無論是作為根結點,還是列表結點,都可以在這里體現。ALLOC_ZVAL在分配了內存后會調用GC_ZVAL_INIT用來初始化替代了zval的zval_gc_info,它會把zval_gc_info中的成員u的buffered字段設置成NULL,此字段僅在將其放入垃圾回收緩沖區時才會有值,否則會一直是NULL。由于PHP中所有的變量都是以zval變量的形式存在,這里以zval_gc_info替換zval,從而成功實現垃圾收集機制在原有系統中的集成。 PHP的垃圾回收機制在PHP5.3中默認為開啟,但是我們可以通過配置文件直接設置為禁用,其對應的配置字段為:zend.enable_gc。在php.ini文件中默認是沒有這個字段的,如果我們需要禁用此功能,則在php.ini中添加zend.enable_gc=0或zend.enable_gc=off。除了修改php.ini配置zend.enable_gc,也可以通過調用gc_enable()/gc_disable()函數來打開/關閉垃圾回收機制。這些函數的調用效果與修改配置項來打開或關閉垃圾回收機制的效果是一樣的。除了這兩個函數PHP提供了gc_collect_cycles()函數可以在根緩沖區還沒滿時強制執行周期回收。與垃圾回收機制是否開啟在PHP源碼中有一些相關的操作和字段。在zend.c文件中有如下代碼: ? 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; } /* }}} */ ? ZEND_INI_BEGIN() ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting) STD_ZEND_INI_BOOLEAN("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, gc_enabled, zend_gc_globals, gc_globals) #ifdef ZEND_MULTIBYTE STD_ZEND_INI_BOOLEAN("detect_unicode", "1", ZEND_INI_ALL, OnUpdateBool, detect_unicode, zend_compiler_globals, compiler_globals) #endif ZEND_INI_END() zend.enable_gc對應的操作函數為ZEND_INI_MH(OnUpdateGCEnabled),如果開啟了垃圾回收機制,即GC_G(gc_enabled)為真,則會調用gc_init函數執行垃圾回收機制的初始化操作。gc_init函數在zend/zend_gc.c 121行,此函數會判斷是否開啟垃圾回收機制,如果開啟,則初始化整個機制,即直接調用malloc給整個緩存列表分配10000個gc_root_buffer內存空間。這里的10000是硬編碼在代碼中的,以宏GC_ROOT_BUFFER_MAX_ENTRIES存在,如果需要修改這個值,則需要修改源碼,重新編譯PHP。gc_init函數在預分配內存后調用gc_reset函數重置整個機制用到的一些全局變量,如設置gc運行的次數統計(gc_runs)和gc中垃圾的個數(collected)為0,設置雙向鏈表頭結點的上一個結點和下一個結點指向自己等。除了這種提的一些用于垃圾回收機制的全局變量,還有其它一些使用較多的變量,部分說明如下: typedef struct _zend_gc_globals { zend_bool gc_enabled; /* 是否開啟垃圾收集機制 */ zend_bool gc_active; /* 是否正在進行 */ ? gc_root_buffer *buf; /* 預分配的緩沖區數組,默認為10000(preallocated arrays of buffers) */ gc_root_buffer roots; /* 列表的根結點(list of possible roots of cycles) */ gc_root_buffer *unused; /* 沒有使用過的緩沖區列表(list of unused buffers) */ gc_root_buffer *first_unused; /* 指向第一個沒有使用過的緩沖區結點(pointer to first unused buffer) */ gc_root_buffer *last_unused; /* 指向最后一個沒有使用過的緩沖區結點,此處為標記結束用(pointer to last unused buffer) */ ? zval_gc_info *zval_to_free; /* 將要釋放的zval變量的臨時列表(temporaryt list of zvals to free) */ zval_gc_info *free_list; /* 臨時變量,需要釋放的列表開頭 */ zval_gc_info *next_to_free; /* 臨時變量,下一個將要釋放的變量位置*/ ? zend_uint gc_runs; /* gc運行的次數統計 */ zend_uint collected; /* gc中垃圾的個數 */ ? // 省略... } 當我們使用一個unset操作想清除這個變量所占的內存時(可能只是引用計數減一),會從當前符號的哈希表中刪除變量名對應的項,在所有的操作執行完后,并對從符號表中刪除的項調用一個析構函數,臨時變量會調用zval_dtor,一般的變量會調用zval_ptr_dtor。 > 當然我們無法在PHP的函數集中找到unset函數,因為它是一種語言結構。 其對應的中間代碼為ZEND_UNSET,在Zend/zend_vm_execute.h文件中你可以找到與它相關的實現。 zval_ptr_dtor并不是一個函數,只是一個長得有點像函數的宏。在Zend/zend_variables.h文件中,這個宏指向函數_zval_ptr_dtor。在Zend/zend_execute_API.c 424行,函數相關代碼如下: ZEND_API void _zval_ptr_dtor(zval **zval_ptr ZEND_FILE_LINE_DC) /* {{{ */ { #if DEBUG_ZEND>=2 [printf](http://www.opengroup.org/onlinepubs/009695399/functions/printf.html)("Reducing refcount for %x (%x): %d->%d\n", *zval_ptr, zval_ptr, Z_REFCOUNT_PP(zval_ptr), Z_REFCOUNT_PP(zval_ptr) - 1); #endif Z_DELREF_PP(zval_ptr); if (Z_REFCOUNT_PP(zval_ptr) == 0) { TSRMLS_FETCH(); ? if (*zval_ptr != &EG(uninitialized_zval)) { GC_REMOVE_ZVAL_FROM_BUFFER(*zval_ptr); zval_dtor(*zval_ptr); efree_rel(*zval_ptr); } } else { TSRMLS_FETCH(); ? if (Z_REFCOUNT_PP(zval_ptr) == 1) { Z_UNSET_ISREF_PP(zval_ptr); } ? GC_ZVAL_CHECK_POSSIBLE_ROOT(*zval_ptr); } } /* }}} */ 從代碼我們可以很清晰的看出這個zval的析構過程,關于引用計數字段做了以下兩個操作: - 如果變量的引用計數為1,即減一后引用計數為0,直接清除變量。如果當前變量如果被緩存,則需要清除緩存 - 如果變量的引用計數大于1,即減一后引用計數大于0,則將變量放入垃圾列表。如果變更存在引用,則去掉其引用。 將變量放入垃圾列表的操作是GC_ZVAL_CHECK_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); } } } 在前面說到gc_zval_check_possible_root函數僅對數組和對象執行垃圾回收操作,然而在gc_zval_possible_root函數中,針對對象類型的變量會去調用GC_ZOBJ_CHECK_POSSIBLE_ROOT宏。而對于其它的可用于垃圾回收的機制的變量類型其調用過程如下: - 檢查zval結點信息是否已經放入到結點緩沖區,如果已經放入到結點緩沖區,則直接返回,這樣可以優化其性能。然后處理對象結點,直接返回,不再執行后面的操作 - 判斷結點是否已經被標記為紫色,如果為紫色則不再添加到結點緩沖區,此處在于保證一個結點只執行一次添加到緩沖區的操作。 - 將結點的顏色標記為紫色,表示此結點已經添加到緩沖區,下次不用再做添加 - 找出新的結點的位置,如果緩沖區滿了,則執行垃圾回收操作。 - 將新的結點添加到緩沖區所在的雙向鏈表。 在gc_zval_possible_root函數中,當緩沖區滿時,程序調用gc_collect_cycles函數,執行垃圾回收操作。其中最關鍵的幾步就是: - 第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列表中。此列表在后面的操作中有用到。 PHP的垃圾回收機制在執行過程中以四種顏色標記狀態。 - GC_WHITE 白色表示垃圾 - GC_PURPLE 紫色表示已放入緩沖區 - GC_GREY 灰色表示已經進行了一次refcount的減一操作 - GC_BLACK 黑色是默認顏色,正常 相關的標記以及操作代碼如下: #define GC_COLOR 0x03 ? #define GC_BLACK 0x00 #define GC_WHITE 0x01 #define GC_GREY 0x02 #define GC_PURPLE 0x03 ? #define GC_ADDRESS(v) \ ((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR)) #define GC_SET_ADDRESS(v, a) \ (v) = ((gc_root_buffer*)((((zend_uintptr_t)(v)) & GC_COLOR) | ((zend_uintptr_t)(a)))) #define GC_GET_COLOR(v) \ (((zend_uintptr_t)(v)) & GC_COLOR) #define GC_SET_COLOR(v, c) \ (v) = ((gc_root_buffer*)((((zend_uintptr_t)(v)) & ~GC_COLOR) | (c))) #define GC_SET_BLACK(v) \ (v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR)) #define GC_SET_PURPLE(v) \ (v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) | GC_PURPLE)) 以上的這種以位來標記狀態的方式在PHP的源碼中使用頻率較高,如內存管理等都有用到,這是一種比較高效及節省的方案。但是在我們做數據庫設計時可能對于字段不能使用這種方式,應該是以一種更加直觀,更加具有可讀性的方式實現。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看