前言
本文的目的不僅僅是總結PHP的垃圾回收機制,而是要發散思維,從垃圾回收機制中得到更多的細節。
什麼是垃圾回收
垃圾回收機制廣泛存在於各種編程語言中,由於每個變量被定義都會佔用一部分內存空間,如果這些空間長期不被釋放,系統的內存就會出現不足的情況,爲了保證系統能在軟件運行過程中有足夠的內存使用,就需要這樣一套回收機制,釋放掉不需要使用的內存空間。
高級語言一般在自動實現了垃圾回收,不需要我們主動去控制。
PHP的垃圾回收實現
php5.3的zval
struct _zval_struct {
union {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
其中的refcount__gc
和is_ref__gc
負責垃圾回收機制的計數,這裏被官方成爲"引用計數".
php7的zval
struct _zval_struct {
union {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} value;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
} u2;
};
zend_refcounted用來實現垃圾回收。
從PHP7開始, 對於在zval的value字段中能保存下的值, 就不再對他們進行引用計數了, 而是在拷貝的時候直接賦值, 這樣就省掉了大量的引用計數相關的操作, 這部分類型有:
- IS_LONG
- IS_DOUBLE
對於那種根本沒有值, 只有類型的類型, 也不需要引用計數了:
- IS_NULL
- IS_FALSE
- IS_TRUE
而對於複雜類型, 一個size_t保存不下的, 那麼我們就用value來保存一個指針, 這個指針指向這個具體的值, 引用計數也隨之作用於這個值上, 而不在是作用於zval上了.
IS_ARRAY的結構如下
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar reserve)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};
其中zend_refcounted_h保存了垃圾回收相關的結構
typedef struct _zend_refcounted_h {
uint32_t refcount; /* reference counter 32-bit */
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type,
zend_uchar flags, /* used for strings & objects */
uint16_t gc_info) /* keeps GC root number (or 0) and color */
} v;
uint32_t type_info;
} u;
} zend_refcounted_h;
這樣,PHP7的垃圾回收是通過refcount
來控制的,也是引用計數。
官方文檔
官方文檔的記錄還是php5.2到5.3的垃圾回收機制,基本原理不變,但是控制的變量名稱變了。
當引用計數變成0,系統認爲變量沒有用處,就會被垃圾回收。
參考資料
- https://www.laruence.com/2018/04/08/3170.html
- https://www.php.net/manual/zh/features.gc.refcounting-basics.php