淺談PHP7的垃圾回收機制

最近一直在看關於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           //引用計數 
}

再貼一點兒代碼再形象的說一下

<?php
$a = 'hello';
echo xdebug_debug_zval('a');
$b = a;
echo xdebug_debug_zval('a');
$c = a;
echo xdebug_debug_zval('a');
unset( $c );
echo xdebug_debug_zval('a');

運行結果:

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徹底完成後才銷燬。

這次大概就說這麼多吧,後面有新的知識點或是這次考慮不全的下次再做一些補充和修改吧,畢竟本人只是一個小菜鳥!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章