PHP内存回收原理

每个php变量存在一个叫”zval”的变量容器中。除了包含变量的类型和值,还包括两个字节的额外信息。is_ref 是个bool值,用来标识这个变量是否是属于引用集合(reference set),refcount 用以表示指向这个zval变量容器的变量(也称符号即symbol)个数

refcount 为0时变量从内存中删除。在5.3之前的版本无法处理循环引用的问题。5.3及以后, 在引入新的垃圾回收算法来对付循环引用计数的时候, 作者加入了大量的宏来操作refcount, 为了能让错误更快的显现, 所以改名为refcount__gc, 迫使大家都使用宏来操作refcount。

一个zval在5.3之前版本占用24字节(64位系统,下同),5.3为了解决循环引用的问题,用zval_gc_info劫持了zval的分配,因此5.3到5.6,一个zval实际占用32字节。

从PHP7开始, 对于在zval的value字段中能保存下的值, 就不再对他们进行引用计数了, 而是在拷贝的时候直接赋值, 这样就省掉了大量的引用计数相关的操作, 这部分类型有:IS_LONG、IS_DOUBLE,对于那种根本没有值, 只有类型的类型, 也不需要引用计数了:IS_NULL、IS_FALSE、IS_TRUE。

PHP7的性能,我们并没有引入什么新的技术模式, 不过就是主要来自, 持续不懈的降低内存占用, 提高缓存友好性, 降低执行的指令数的这些原则而来的。

PHP5(v<5.3) zval 结构体定义:

struct _zval_struct {
    union {
        long lval;					/* long value */
        double dval;				/* double value */
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;				/* hash table value */
        zend_object_value obj;
    } value;
    unsigned int refcount;
    unsigned char type;
    unsigned char is_ref;
};

PHP5(5.3<= v < 7) zval 结构体定义:

struct _zval_struct {
    union {
        long lval;					/* long value */
        double dval;				/* double value */
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;				/* hash table value */
        zend_object_value obj;
    } value;
    unsigned int refcount__gc;
    unsigned char type;
    unsigned char is_ref__gc;
};

5.3及以上版本虽然 struct  _zval_struct 结构体的内容没有变(除了 refcount 重命名为 refcount__gc,is_ref 重命名为 is_ref__gc),但是新增的头文件 zend_gc.h 重定义了 ALLOC_ZVAL 宏

#undef  ALLOC_ZVAL
#define ALLOC_ZVAL(z) 									\
	do {												\
		(z) = (zval*)emalloc(sizeof(zval_gc_info));		\
		GC_ZVAL_INIT(z);								\
	} while (0)

zval_gc_info 的定义为

typedef struct _zval_gc_info {
	zval z;
	union {
		gc_root_buffer       *buffered;
		struct _zval_gc_info *next;
	} u;
} zval_gc_info;

所以实际上5.3及以上版本zval大小为32字节。

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;            /* 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;
};

 

关于内存对齐请参考:https://levphy.github.io/2017/03/23/memory-alignment.html

1 评论

  1. 内存对齐的3大规则:
    1 对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍
    2 结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
    3 如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注