1. 程式人生 > 實用技巧 >面試官:什麼是PHP的垃圾回收機制 PHP高階面試題+詳解

面試官:什麼是PHP的垃圾回收機制 PHP高階面試題+詳解

ps:本篇內容包括精選面試題與知識篇。

PHP面試題關於PHP的垃圾回收機制,PHP的垃圾回收機制引用計數 (reference counting) GC 機制,PHP可以自動進行記憶體管理,清除不需要的物件,PHP面試題分享PHP關於垃圾回收機制的面試題:

★我的php學習交流社群——1023755567。群內管理已準備好 整理好的BAT等一線大廠進階知識體系備好(相關學習資料以及筆面試題)歡迎獲取一起晉升=點選加

面試題篇

  • 介紹一下PHP的垃圾回收機制

PHP使用了引用計數(reference counting)GC機制,同時使用根緩衝區機制,當php發現有存在迴圈引用的zval時,就會把其投入到根緩衝區,當根緩衝區達到配置檔案中的指定數量後,就會進行垃圾回收,以此解決迴圈引用導致的記憶體洩漏問題。

  • 1. 如果引用計數減少到零,所在變數容器將被清除(free),不屬於垃圾;
  • 2. 如果一個zval的引用計數減少後還大於0,那麼它會進入垃圾週期。其次,在一個垃圾週期中,通過檢查引用計數是否減1,並且檢查哪些變數容器的引用次數是零,來發現哪部分是垃圾。

每個物件都內含一個引用計數器refcount,每個reference連線到物件,計數器加1。當reference離開生存空間或被設為 NULL,計數器減1。當某個物件的引用計數器為零時,PHP知道你將不再需要使用這個物件,釋放其所佔的記憶體空間。

  • 下列關於PHP垃圾回收的說法,錯誤的是?

A、開啟/關閉垃圾回收機制可以通過修改php配置實現

B、可以在程式中使用gc_enable() 和 gc_disable()開啟和關閉。

C、PHP中的垃圾回收機制,會大幅度提升系統性能。

D、開啟垃圾回收機制後,針對記憶體洩露的情況,可以節省大量的記憶體空間,但是由於垃圾回收演算法執行耗費時間,開啟垃圾回收演算法會增加指令碼的執行時間。

參考答案:C
答案解析:PHP中的垃圾回收機制,僅僅在迴圈回收演算法確實執行時會有時間消耗上的增加。但是在平常的(更小的)指令碼中應根本就沒有效能影響。

  • php垃圾回收機制的說法錯誤的是?

A、在一個垃圾週期中,通過檢查引用計數是否減1,並且檢查哪些變數容器的引用次數是零,來發現哪部分是垃圾

B、可以通過呼叫gc_enable() 和 gc_disable()函式來開啟和關閉垃圾回收機制

C、通過清理未被使用的變數來節省記憶體的佔用

D、php程式碼執行完畢後會自動執行垃圾回收,所以不需要手動執行垃圾回收

參考答案:D
答案解析:php一段程式碼有可能要長時間執行,但若此期間有未引用的變數的話,就會佔用記憶體的空間,導致執行緩慢等問題

知識篇

一、概念

垃圾回收是一個多數程式語言中都帶有的記憶體管理機制。與非託管性語言相反:C, C++ 和 Objective C,使用者需要手動收集記憶體,帶有 GC 機制的語言:Java, javaScript 和 PHP 可以自動管理記憶體。

垃圾回收機制(gc)顧名思義,就是廢物重利用的意思,是一種動態儲存分配的方案。它會自動釋放程式不再需要的已分配的記憶體塊。垃圾回收機制可以讓程式設計師不必過分關心程式記憶體分配,從而將更多的精力投入到業務邏輯。

在現在的流行各種語言當中,垃圾回收機制是新一代語言所共有的特徵,如Python、PHP、C#、Ruby等都使用了垃圾回收機制。

二、PHP垃圾回收機制

1、在PHP5.3版本之前,使用的垃圾回收機制是單純的“引用計數”。

什麼叫做引用計數?
由於PHP是用C來寫的,C裡面有一種東西叫做結構體,我們PHP的變數在C中就是用這種方式儲存的。
每個PHP的變數都存在於一個叫做zval的容器中,一個zval容器,除了包含變數名和值,還包括兩個位元組的額外資訊:
● 一個叫做'is_ref',是個布林值,用來表示這個變數是否屬於引用集合,通過這個位元組,我們php才能把普通變數和引用變數區分開來。
● 第二個額外位元組就是'refcount',用來表示指向這個容器的變數的個數。

即:

① 每個記憶體物件都分配一個計數器,當記憶體物件被變數引用時,計數器+1;

② 當變數引用撤掉後(執行unset()後),計數器-1;

③ 當計數器=0時,表明記憶體物件沒有被使用,該記憶體物件則進行銷燬,垃圾回收完成。

並且PHP在一個生命週期結束後就會釋放此程序/執行緒所佔的內容,這種方式決定了PHP在前期不需要過多考慮記憶體的洩露問題。

但是當兩個或多個物件互相引用形成環狀後,記憶體物件的計數器則不會消減為0;這時候,這一組記憶體物件已經沒用了,但是不能回收,從而導致記憶體洩露的現象。

php5.3開始,使用了新的垃圾回收機制,在引用計數基礎上,實現了一種複雜的演算法,來檢測記憶體物件中引用環的存在,以避免記憶體洩露。

  • 2、隨著PHP的發展,PHP開發者的增加以及其所承載的業務範圍的擴大,在PHP5.3中引入了更加完善的垃圾回收機制,新的垃圾回收機制解決了無法處理迴圈的引用記憶體洩漏問題。

如官方文件所說:每個php變數存在一個叫"zval"的變數容器中。一個zval變數容器,除了包含變數的型別和值,還包括兩個位元組的額外資訊。第一個是"is_ref",是個bool值,用來標識這個變數是否是屬於引用集合(reference set)。通過這個位元組,php引擎才能把普通變數和引用變數區分開來,由於php允許使用者通過使用&來使用自定義引用,zval變數容器中還有一個內部引用計數機制,來優化記憶體使用。

第二個額外位元組是"refcount",用以表示指向這個zval變數容器的變數(也稱符號即symbol)個數。所有的符號存在一個符號表中,其中每個符號都有作用域(scope)。

官方文件所說,可以使用Xdebug來檢查引用計數情況:

<?php
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
unset( $b, $c );
xdebug_debug_zval( 'a' );
?>

以上例程會輸出:

a: (refcount=3, is_ref=0)='new string'
a: (refcount=1, is_ref=0)='new string'

注意:從PHP7的NTS版本開始,以上例程的引用將不再被計數,即$c=$b=$a之後a的引用計數也是1.具體分類如下:

在PHP 7中,zval可以被引用計數或不被引用。在zval結構中有一個標誌確定了這一點。

① 對於null,bool,int和double的型別變數,refcount永遠不會計數;

② 對於物件、資源型別,refcount計數和php5的一致;

③ 對於字串,未被引用的變數被稱為“實際字串”。而那些被引用的字串被重複刪除(即只有一個帶有特定內容的被插入的字串)並保證在請求的整個持續時間記憶體在,所以不需要為它們使用引用計數;如果使用了opcache,這些字串將存在於共享記憶體中,在這種情況下,您不能使用引用計數(因為我們的引用計數機制是非原子的);

④對於陣列,未引用的變數被稱為“不可變陣列”。其陣列本身計數與php5一致,但是數組裡面的每個鍵值對的計數,則按前面三條的規則(即如果是字串也不在計數);如果使用opcache,則程式碼中的常量陣列文字將被轉換為不可變陣列。

再次,這些生活在共享記憶體,因此不能使用refcounting。

我們的demo例子如下:

<?php
echo '測試字串引用計數';
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
unset( $b);
xdebug_debug_zval( 'a' );
$b = &$a;
xdebug_debug_zval( 'a' );
echo '測試陣列引用計數';
$c = array('a','b');
xdebug_debug_zval( 'c' );
$d = $c;
xdebug_debug_zval( 'c' );
$c[2]='c';
xdebug_debug_zval( 'c' );
echo '測試int型計數';
$e = 1;
xdebug_debug_zval( 'e' );

看到的輸出如下:

三、回收週期

預設的,PHP的垃圾回收機制是開啟的,然後有個php.ini設定允許你修改它:zend.enable_gc 。

當垃圾回收機制開啟時,演算法會判斷每當根快取區存滿時,就會執行迴圈查詢。根快取區有固定的大小,預設10,000,可以通過修改PHP原始碼檔案Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然後重新編譯PHP,來修改這個值。當垃圾回收機制關閉時,迴圈查詢演算法永不執行,然而,根將一直存在根緩衝區中,不管在配置中垃圾回收機制是否啟用。

除了修改配置zend.enable_gc ,也能通過分別呼叫gc_enable() 和 gc_disable()函式在執行php時來開啟和關閉垃圾回收機制。呼叫這些函式,與修改配置項來開啟或關閉垃圾回收機制的效果是一樣的。即使在可能根緩衝區還沒滿時,也能強制執行週期回收。你能呼叫gc_collect_cycles()函式達到這個目的。這個函式將返回使用這個演算法回收的週期數。

允許開啟和關閉垃圾回收機制並且允許自主的初始化的原因,是由於你的應用程式的某部分可能是高時效性的。在這種情況下,你可能不想使用垃圾回收機制。當然,對你的應用程式的某部分關閉垃圾回收機制,是在冒著可能記憶體洩漏的風險,因為一些可能根也許存不進有限的根緩衝區。

因此,就在你呼叫gc_disable()函式釋放記憶體之前,先呼叫gc_collect_cycles()函式可能比較明智。因為這將清除已存放在根緩衝區中的所有可能根,然後在垃圾回收機制被關閉時,可留下空緩衝區以有更多空間儲存可能根。

四、效能影響

1、記憶體佔用空間的節省

首先,實現垃圾回收機制的整個原因是為了一旦先決條件滿足,通過清理迴圈引用的變數來節省記憶體佔用。在PHP執行中,一旦根緩衝區滿了或者呼叫gc_collect_cycles() 函式時,就會執行垃圾回收。

2、執行時間增加

垃圾回收影響效能的第二個領域是它釋放已洩漏的記憶體耗費的時間。

通常,PHP中的垃圾回收機制,僅僅在迴圈回收演算法確實執行時會有時間消耗上的增加。但是在平常的(更小的)指令碼中應根本就沒有效能影響。

3、在平常指令碼中有迴圈回收機制執行的情況下,記憶體的節省將允許更多這種指令碼同時執行在你的伺服器上。因為總共使用的記憶體沒達到上限。

這種好處在長時間執行指令碼中尤其明顯,諸如長時間的測試套件或者daemon指令碼此類。同時,對通常比Web指令碼執行時間長的指令碼應用程式,新的垃圾回收機制,應該會大大改變一直以來認為記憶體洩漏問題難以解決的看法。

最後,祝所有大家在面試中過關斬將,拿到心儀offer。

對此我整理了一些資料,包括但不限於:分散式架構、高可擴充套件、高效能、高併發、伺服器效能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell指令碼、Docker、微服務、Nginx等多個知識點高階進階乾貨需要的可以免費分享給大家

如果想與一群3-8年資深開發者一起交流學習的話,需要,我的官方群-點選此處

學習資料、文件、面試題、視訊 點選連結免費獲取​shimo.im