您的位置 首页 php

php7的垃圾回收机制(一)

这里以php7版本进行讨论

为什么要进行垃圾回收

当程序中的变量不再被使用时,应该及时释放掉所占用的内存空间,否则可能会造成内存泄露,这也是PHP程序员在日常的工作中不太关注的问题,因为PHP的底层已经处理好这个问题了。

php的垃圾回收主要通过两方面进行

  • 通过变量的引用计数进行处理
  • 自动gc机制处理数组、对象等复杂类型的变量回收

先来看引用计数的实现结构

 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;

struct _zend_refcounted {
	zend_refcounted_h gc;
};

struct _zend_string {
	zend_refcounted_h gc;
	zend_ulong        h;                /* hash value */
	size_t            len;
	char              val[1];
};  

在zend_string / zend_array 等头部都有gc, 这个来统计这个value值被指向的次数,zend_refounted_h 包括一个 uint32_t 4字节的统计值,还有一个 4字节的u, 总共占8字节。

引用计数用来表示一个zval被指向(引用)的数量

我们使用之前的例子来测试,关于zend_string 的介绍可以看这里

 <?php
$a = time()."hello";
echo $a;

$b = $a;

echo $b;  

引用计数的作用

 
gdb /home/php7.2.5/debug/bin/php
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-110.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<
Reading symbols from /home/php7.2.5/debug/bin/php...done.
# 在echo处理函数出打断点
# ZEND_ECHO_SPEC_CV_HANDLER是echo $变量时的处理函数

(gdb) b ZEND_ECHO_SPEC_CV_HANDLER
Breakpoint 1 at 0x973289: file /root/php-7.2.5/Zend/zend_vm_execute.h, line 33086.

# 执行这个文件
(gdb) run s.php
Starting program: /home/php7.2.5/debug/bin/php s.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

# 到了断点  echo $a
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /root/php-7.2.5/Zend/zend_vm_execute.h:33086
33086		SAVE_OPLINE();
Missing separate debuginfos, use: debuginfo-install glibc-2.17-222.el7.x86_64 libxml2-2.9.1-6.el7_2.3.x86_64 nss-softokn-freebl-3.36.0-5.el7_5.x86_64 xz-libs-5.2.2-1.el7.x86_64 zlib-1.2.7-17.el7.x86_64

#下一步
(gdb) n
33087		z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33089		if (Z_TYPE_P(z) == IS_STRING) {
(gdb) n
33090			zend_string *str = Z_STR_P(z);
  
# 打印这个zval  
(gdb) p *z
$1 = {value = {lval = 140737318935600, dval = 6.9533474373882711e-310, counted = 0x7ffff5e6dc30, str = 0x7ffff5e6dc30, arr = 0x7ffff5e6dc30, obj = 0x7ffff5e6dc30,
    res = 0x7ffff5e6dc30, ref = 0x7ffff5e6dc30, ast = 0x7ffff5e6dc30, zv = 0x7ffff5e6dc30, ptr = 0x7ffff5e6dc30, ce = 0x7ffff5e6dc30, func = 0x7ffff5e6dc30, ww = {
      w1 = 4125547568, w2 = 32767}}, u1 = {v = {type = 6 '\006', type_flags = 20 '\024', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 5126}, u2 = {
    next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
#可以看到u1.v.type=6 是字符串
#打印这个字符串
(gdb) p *$1.value.str
#可以看到refcount=1
#也就是当前有一个zval指向这个str
$2 = {gc = {refcount = 1, u = {v = {type = 6 '\006', flags = 0 '\000', gc_info = 0}, type_info = 6}}, h = 0, len = 15, val = "1"}

#打印值看一下
(gdb) p *$2.val@15
$3 = "1602989805hello"

# 继续往下执行
(gdb) n
33092			if (ZSTR_LEN(str) != 0) {
(gdb) n
33093				zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
(gdb) n
1602989805hello33106		ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
(gdb) n
33107	}
(gdb) n
execute_ex (ex=0x7ffff5e1e030) at /root/php-7.2.5/Zend/zend_vm_execute.h:61928
61928					HYBRID_BREAK();
(gdb) n

# $b = $a
62788					ZEND_ASSIGN_SPEC_CV_CV_RETVAL_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
(gdb) n
62789					HYBRID_BREAK();
(gdb) n
61927					ZEND_ECHO_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
(gdb) n

# 第二个echo $b
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /root/php-7.2.5/Zend/zend_vm_execute.h:33086
33086		SAVE_OPLINE();
(gdb) n
33087		z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33089		if (Z_TYPE_P(z) == IS_STRING) {
(gdb) n
33090			zend_string *str = Z_STR_P(z);

# 这里可以看到 z跟上面那个是一个 
(gdb) p *z
$4 = {value = {lval = 140737318935600, dval = 6.9533474373882711e-310, counted = 0x7ffff5e6dc30, str = 0x7ffff5e6dc30, arr = 0x7ffff5e6dc30, obj = 0x7ffff5e6dc30,
    res = 0x7ffff5e6dc30, ref = 0x7ffff5e6dc30, ast = 0x7ffff5e6dc30, zv = 0x7ffff5e6dc30, ptr = 0x7ffff5e6dc30, ce = 0x7ffff5e6dc30, func = 0x7ffff5e6dc30, ww = {
      w1 = 4125547568, w2 = 32767}}, u1 = {v = {type = 6 '\006', type_flags = 20 '\024', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 5126}, u2 = {
    next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}

(gdb) p *$4.value.str
# 打印下看到refcount=2 
# $b = $a 执行之后, $a, $b 同时指向这个zend_str
$5 = {gc = {refcount = 2, u = {v = {type = 6 '\006', flags = 0 '\000', gc_info = 0}, type_info = 6}}, h = 0, len = 15, val = "1"}

(gdb) p *$5.val@15
$6 = "1602989805hello"
  

这里仅限于数组、对象、非常量字符串,如果是字符串常量,则不会产生引用计数的变化来,看一下。

 <?php
$a = "hello";
echo $a;

$b = $a;

echo $b;  

这里 hello会被当做常量处理,它的refcount始终为0,只有当请求处理完才会被销毁

 (gdb) b ZEND_ECHO_SPEC_CV_HANDLER
Breakpoint 1 at 0x973289: file /root/php-7.2.5/Zend/zend_vm_execute.h, line 33086.
(gdb) run s.php
Starting program: /home/php7.2.5/debug/bin/php s.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /root/php-7.2.5/Zend/zend_vm_execute.h:33086
33086		SAVE_OPLINE();
Missing separate debuginfos, use: debuginfo-install glibc-2.17-222.el7.x86_64 libxml2-2.9.1-6.el7_2.3.x86_64 nss-softokn-freebl-3.36.0-5.el7_5.x86_64 xz-libs-5.2.2-1.el7.x86_64 zlib-1.2.7-17.el7.x86_64
(gdb) n
33087		z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33089		if (Z_TYPE_P(z) == IS_STRING) {
(gdb) p *z
$1 = {value = {lval = 140737318497088, dval = 6.9533474157228996e-310, counted = 0x7ffff5e02b40, str = 0x7ffff5e02b40, arr = 0x7ffff5e02b40, obj = 0x7ffff5e02b40,
    res = 0x7ffff5e02b40, ref = 0x7ffff5e02b40, ast = 0x7ffff5e02b40, zv = 0x7ffff5e02b40, ptr = 0x7ffff5e02b40, ce = 0x7ffff5e02b40, func = 0x7ffff5e02b40, ww = {
      w1 = 4125109056, w2 = 32767}}, u1 = {v = {type = 6 '\006', type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 6}, u2 = {
    next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p *$1.value.str
# refcount  = 0  此时 $a = "hello"
$2 = {gc = {refcount = 0, u = {v = {type = 6 '\006', flags = 2 '\002', gc_info = 0}, type_info = 518}}, h = 9223372247569412249, len = 5, val = "h"}
(gdb) p *$2.val@5
$3 = "hello"
(gdb) n
33090			zend_string *str = Z_STR_P(z);
(gdb) n
33092			if (ZSTR_LEN(str) != 0) {
(gdb) n
33093				zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
(gdb) n
hello33106		ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
(gdb) n
33107	}
(gdb) n
execute_ex (ex=0x7ffff5e1e030) at /root/php-7.2.5/Zend/zend_vm_execute.h:61928
61928					HYBRID_BREAK();
(gdb) n
# $b = $a
62788					ZEND_ASSIGN_SPEC_CV_CV_RETVAL_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
(gdb) n
62789					HYBRID_BREAK();
(gdb) n
# echo $b;
61927					ZEND_ECHO_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
(gdb) n

Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /root/php-7.2.5/Zend/zend_vm_execute.h:33086
33086		SAVE_OPLINE();
(gdb) n
33087		z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33089		if (Z_TYPE_P(z) == IS_STRING) {
(gdb) p *z
# 还是这个zend_str
$4 = {value = {lval = 140737318497088, dval = 6.9533474157228996e-310, counted = 0x7ffff5e02b40, str = 0x7ffff5e02b40, arr = 0x7ffff5e02b40, obj = 0x7ffff5e02b40,
    res = 0x7ffff5e02b40, ref = 0x7ffff5e02b40, ast = 0x7ffff5e02b40, zv = 0x7ffff5e02b40, ptr = 0x7ffff5e02b40, ce = 0x7ffff5e02b40, func = 0x7ffff5e02b40, ww = {
      w1 = 4125109056, w2 = 32767}}, u1 = {v = {type = 6 '\006', type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 6}, u2 = {
    next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p *$4.value.str
# 引用计数 refcount = 0
$5 = {gc = {refcount = 0, u = {v = {type = 6 '\006', flags = 2 '\002', gc_info = 0}, type_info = 518}}, h = 9223372247569412249, len = 5, val = "h"}
(gdb)  

我们用数字再来看一下,数字int /double 是直接存在zend_value中的,不存在refcount这种说法, 直接通过值拷贝进行。

 
<?php
$a = 1;
echo $a;

$b = $a;

echo $b;  
 (gdb) b ZEND_ECHO_SPEC_CV_HANDLER
Breakpoint 1 at 0x973289: file /root/php-7.2.5/Zend/zend_vm_execute.h, line 33086.
(gdb) n
The program is not being run.
(gdb) run s.php
Starting program: /home/php7.2.5/debug/bin/php s.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /root/php-7.2.5/Zend/zend_vm_execute.h:33086
33086		SAVE_OPLINE();
Missing separate debuginfos, use: debuginfo-install glibc-2.17-222.el7.x86_64 libxml2-2.9.1-6.el7_2.3.x86_64 nss-softokn-freebl-3.36.0-5.el7_5.x86_64 xz-libs-5.2.2-1.el7.x86_64 zlib-1.2.7-17.el7.x86_64
(gdb) n
33087		z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33089		if (Z_TYPE_P(z) == IS_STRING) {
(gdb) n
33096			zend_string *str = _zval_get_string_func(z);
(gdb) p *z

# u1.v.type = 4 也就是is_long 类型的,值直接存在了vlaue.lval中  
$1 = {value = {lval = 1, dval = 4.9406564584124654e-324, counted = 0x1, str = 0x1, arr = 0x1, obj = 0x1, res = 0x1, ref = 0x1, ast = 0x1, zv = 0x1, ptr = 0x1,
    ce = 0x1, func = 0x1, ww = {w1 = 1, w2 = 0}}, u1 = {v = {type = 4 '\004', type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 4},
  u2 = {next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p $1.value.lval
$2 = 1

(gdb) n
33098			if (ZSTR_LEN(str) != 0) {
(gdb) n
33099				zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
(gdb) n
133103			zend_string_release(str);
(gdb) n
33106		ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
(gdb) n
33107	}
(gdb) n
execute_ex (ex=0x7ffff5e1e030) at /root/php-7.2.5/Zend/zend_vm_execute.h:61928
61928					HYBRID_BREAK();
(gdb) n
# $b = $a
62788					ZEND_ASSIGN_SPEC_CV_CV_RETVAL_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
(gdb) n
62789					HYBRID_BREAK();
(gdb) n
61927					ZEND_ECHO_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
(gdb) n

Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /root/php-7.2.5/Zend/zend_vm_execute.h:33086
33086		SAVE_OPLINE();
(gdb) n
33087		z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33089		if (Z_TYPE_P(z) == IS_STRING) {
(gdb) n
33096			zend_string *str = _zval_get_string_func(z);
(gdb) p *z
$3 = {value = {lval = 1, dval = 4.9406564584124654e-324, counted = 0x1, str = 0x1, arr = 0x1, obj = 0x1, res = 0x1, ref = 0x1, ast = 0x1, zv = 0x1, ptr = 0x1,
    ce = 0x1, func = 0x1, ww = {w1 = 1, w2 = 0}}, u1 = {v = {type = 4 '\004', type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 4},
  u2 = {next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb) p &$1
$4 = (zval *) 0x7ffff5e1e080
(gdb) p &$3
$5 = (zval *) 0x7ffff5e1e090
# 上面看到 $1 $3不是同一个内存地址
# 也就是 $b = $a 直接拷贝了一份zend_value
(gdb)

  

 zval.u1.v.type 数据类型

/* regular data types */
#define IS_UNDEF					0
#define IS_NULL						1
#define IS_FALSE					2
#define IS_TRUE						3
#define IS_LONG						4
#define IS_DOUBLE					5
#define IS_STRING					6
#define IS_ARRAY					7
#define IS_OBJECT					8
#define IS_RESOURCE					9
#define IS_REFERENCE				10  

总结:

  • 数组、对象、非常量字符串存在引用计数
  • 数值、布尔类型不存在引用计数
  • 常量字符串引用计数不生效,当做常量处理

文章来源:智云一二三科技

文章标题:php7的垃圾回收机制(一)

文章地址:https://www.zhihuclub.com/79950.shtml

关于作者: 智云科技

热门文章

网站地图