浅谈PHP7的垃圾回收机制

浅谈PHP7的垃圾回收机制_第1张图片

最近一直在看关于PHP得来讲回收机制,今天总结一下,写下来,一来让自己的影响更加深刻一些,二来为后面继续学习swoole先做一点二准备。

在我看来,垃圾回收机制其实说白了就是将内存回收再使用得一个过程。这些东西一般我们在做PHP开发的时候不会遇到,因为PHP本身已经帮我们做好了这些。在了解垃圾回收机制之前,我们先要对内存有一个概念,这块不清楚的建议大家先去了解一下。就想我前面说的,垃圾回收机制就是处理内存的一个过程,那么,在这个过程中,我们就要知道1.引用计数2.写时拷贝。

首先来说说引用计数。大家都知道PHP其实是用C写出来的一门语言,那么,了解C的同学也就知道C中有一个关于结构体的说法,还有一个联合体的说法。那么现在,就有这样两个概念:zval(结构体)和zend_value(联合体)。你可以将zval认为是一个变量,zend_value是一个变量的值。他们的结构如下:

zval {
  string "a"            //变量的名字是a
  value  zend_value     //变量的值
  type   string         //变量是字符串类型
}
zend_value {
  string    "hello916"  //值的内容
  refcount  1           //引用计数 
}

再贴一点儿代码再形象的说一下

运行结果:

a: (refcount=1, is_ref=0)='hello'
a: (refcount=2, is_ref=0)='hello'
a: (refcount=3, is_ref=0)='hello'
a: (refcount=2, is_ref=0)='hello'

上面代码里面的xbebug_debug_zval()方法是用来打印出引用计数

那现在我们就看到了每引用一次,a的refcount就会加一,unset一次refcount就减少了1。这样我们现在就拿到了引用的次数。记住这个次数,很重要!!!关于引用计数暂时就说这些了。

再来说下关于写时拷贝。关于这个东西的概念,我是这样理解的,其实就是指向与复制的差别。还是用代码来说一下:注意两端代码对user2不同的处理方式。

class User{
  public $username = 'test';
}
$user1 = new User();
$user2 = $user1;

本质上user1和user2变量指向的都是同一个PHP对象,占用的内存也只有一份,如果你修改user2的username属性实际上就是在user1的username属性,当然了,修改user1的username属性也是在修改user2的username属性,搞来搞去都是同一个。这个就是我前面说的指向。

class User{
  public $username = 'test';
}
$user1 = new User();
$user2 = clone $user1;

这样的话,表示实打实地复制一个User对象出来,而不是普普通通的引用了,这种情况下,你修改user2的username属性不会影响user1的username属性,修改user1的任何属性也不会影响到user2的属性,说到底是占用了两份内存。

需要知道的是:第二次我们再赋值了一个user之后,如果你没有修改user2里面的username的话,它还是使用了之前的username,只有当你在user2里面重新给username赋值之后,它才会改变,这个也就是写时拷贝。意思就是它会等到你在修改值得时候才发生了正真得变化得。。

弄清楚了这两个概念,我们继续回到之前要说得垃圾回收机制,当一个zval被unset之后或者是走完了这个函数之后,zend引擎就会去判断它得zend_value里面得refcount是不是为0,如是0的话,则直接被释放,若不是0的话,那么这时这个zend_value不能释放,但是,也不能说它就一定就不是垃圾。PHP7中有这样两种情况:

  1. 数组:a数组的某个成员使用&引用a自己

  2. 对象:对象的某个成员引用对象自己

这种情况下,zend_value不能释放,但也不能放过它,不然一定会产生内存泄漏,所以这会儿zend_value会被扔到一个叫做垃圾回收堆中,然后zend引擎会依次对垃圾回收堆中的这些zend_value进行二次检测,检测是不是由于上述两种情况造成的refcount为1但是自身却确实没有人再用了,如果一旦确定是上述两种情况造成的,那么就会将zend_value彻底抹掉释放内存。

那么垃圾回收发生在什么时候?做PHP的都知道,PHP运行一次就销毁了,那我要这gc有什么用?其实不然,首先当一次fpm运行完毕后,最后一定还有gc的,这个销毁就是gc;其次是,内存都是即用即释放的,而不是攒着非得到最后,你想想一个典型的场景,你的控制器里的某个方法里用了一个函数,函数需要一个巨大的数组参数,然后函数还需要修改这个巨大的数组参数,你们应该是在函数的运行范围里面修改这个数组,所以此时会发生写时拷贝了,当函数运行完毕后,就得赶紧释放掉这块儿内存以供给其他进程使用,而不是非得等到本地fpm request彻底完成后才销毁。

这次大概就说这么多吧,后面有新的知识点或是这次考虑不全的下次再做一些补充和修改吧,毕竟本人只是一个小菜鸟!!!

你可能感兴趣的:(PHP)