首頁>技術>

無意中看到了swoole官方新文章在說記憶體洩漏一事。

https://mp.weixin.qq.com/s/D3hc03Wqz3aDuabHH2C46g

總結了一下,大機率的三點:

1 coder一不小心寫了個會導致記憶體洩漏的方法,例子我不舉了,網上搜索一大把。

2 php-fpm記憶體設定值過小,swoole框架註解使用過多,導致記憶體注入了太多內容,一般人都引發不了這問題,此時只需要調大php-fpm預設配置記憶體即可。

3 gc不及時導致,無cli的swoole沒釋放記憶體給系統,我關心的是這最不起眼的點,其中涉及到一個方法php gc_mem_caches(), php版本>7後支援。

搜尋了一個這個方法,確實解釋少得可憐,如下:

(PHP 7, PHP 8)

gc_mem_caches — Reclaims memory used by the Zend Engine memory manager

查查php記憶體相關的文章吧

其實這個引用計數早就不是啥新鮮事了,有興趣瞭解其優勢與缺陷的,連結為你準備好了

https://bk.tw.lvfukeji.com/wiki/%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0

OK迴歸正題

PHP Garbage Collection 第一篇 解釋引用計數基本知識

每個php變數存在一個叫"zval"的變數容器中。一個zval變數容器,除了包含變數的型別和值,還包括兩個位元組的額外資訊。第一個是"is_ref",是個bool值,用來標識這個變數是否是屬於引用集合(reference set)。透過這個位元組,php引擎才能把普通變數和引用變數區分開來,由於php允許使用者透過使用&來使用自定義引用,zval變數容器中還有一個內部引用計數機制,來最佳化記憶體使用。第二個額外位元組是"refcount",用以表示指向這個zval變數容器的變數(也稱符號即symbol)個數。所有的符號存在一個符號表中,其中每個符號都有作用域(scope),那些主指令碼(比如:透過瀏覽器請求的的指令碼)和每個函式或者方法也都有作用域。

當一個變數被賦常量值時,就會生成一個zval變數容器,如下例這樣:

示例 #1 生成一個新的zval容器

<?php$a = "new string";?>

在上例中,新的變數a,是在當前作用域中生成的。並且生成了型別為 string 和值為new string的變數容器。在額外的兩個位元組資訊中,"is_ref"被預設設定為 false,因為沒有任何自定義的引用生成。"refcount" 被設定為 1,因為這裡只有一個變數使用這個變數容器. 注意到當"refcount"的值是1時,"is_ref"的值總是false. 如果你已經安裝了» Xdebug,你能透過呼叫函式 xdebug_debug_zval()顯示"refcount"和"is_ref"的值。

示例 #2 顯示zval資訊

<?phpxdebug_debug_zval('a');?>

以上例程會輸出:

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

把一個變數賦值給另一變數將增加引用次數(refcount).

示例 #3 增加一個zval的引用計數

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

以上例程會輸出:

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

這時,引用次數是2,因為同一個變數容器被變數 a 和變數 b關聯.當沒必要時,php不會去複製已生成的變數容器。變數容器在”refcount“變成0時就被銷燬. 當任何關聯到某個變數容器的變數離開它的作用域(比如:函式執行結束),或者對變數呼叫了函式 unset()時,”refcount“就會減1,下面的例子就能說明:

示例 #4 減少引用計數

<?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'

如果我們現在執行 unset($a);,包含型別和值的這個變數容器就會從記憶體中刪除。

複合型別(Compound Types)

當考慮像 array和object這樣的複合型別時,事情就稍微有點複雜. 與 標量(scalar)型別的值不同,array和 object型別的變數把它們的成員或屬性存在自己的符號表中。這意味著下面的例子將生成三個zval變數容器。

示例 #5 Creating a array zval

<?php$a = array( 'meaning' => 'life', 'number' => 42 );xdebug_debug_zval( 'a' );?>

以上例程的輸出類似於:

a: (refcount=1, is_ref=0)=array (   'meaning' => (refcount=1, is_ref=0)='life',   'number' => (refcount=1, is_ref=0)=42)

圖示:

Creating a array zval

這三個zval變數容器是: a,meaning和 number。增加和減少”refcount”的規則和上面提到的一樣. 下面, 我們在陣列中再新增一個元素,並且把它的值設為陣列中已存在元素的值:

示例 #6 新增一個已經存在的元素到陣列中

<?php$a = array( 'meaning' => 'life', 'number' => 42 );$a['life'] = $a['meaning'];xdebug_debug_zval( 'a' );?>

以上例程的輸出類似於:

a: (refcount=1, is_ref=0)=array (   'meaning' => (refcount=2, is_ref=0)='life',   'number' => (refcount=1, is_ref=0)=42,   'life' => (refcount=2, is_ref=0)='life')

圖示:

從以上的xdebug輸出資訊,我們看到原有的陣列元素和新新增的陣列元素關聯到同一個"refcount"2的zval變數容器. 儘管 Xdebug的輸出顯示兩個值為'life'的 zval 變數容器,其實是同一個。 函式xdebug_debug_zval()不顯示這個資訊,但是你能透過顯示記憶體指標資訊來看到。

<?php$a = array( 'meaning' => 'life', 'number' => 42 );$a['life'] = $a['meaning'];unset( $a['meaning'], $a['number'] );xdebug_debug_zval( 'a' );?>

以上例程的輸出類似於:

a: (refcount=1, is_ref=0)=array (   'life' => (refcount=1, is_ref=0)='life')

現在,當我們新增一個數組本身作為這個陣列的元素時,事情就變得有趣,下個例子將說明這個。例中我們加入了引用運算子,否則php將生成一個複製。

示例 #8 把陣列作為一個元素新增到自己

<?php$a = array( 'one' );$a[] =& $a;xdebug_debug_zval( 'a' );?>

以上例程的輸出類似於:

a: (refcount=2, is_ref=1)=array (   0 => (refcount=1, is_ref=0)='one',   1 => (refcount=2, is_ref=1)=...)

圖示:

能看到陣列變數 (a) 同時也是這個陣列的第二個元素(1) 指向的變數容器中“refcount”為 2。上面的輸出結果中的"..."說明發生了遞迴操作, 顯然在這種情況下意味著"..."指向原始陣列。

跟剛剛一樣,對一個變數呼叫unset,將刪除這個符號,且它指向的變數容器中的引用次數也減1。所以,如果我們在執行完上面的程式碼後,對變數$a呼叫unset, 那麼變數 $a 和陣列元素 "1" 所指向的變數容器的引用次數減1, 從"2"變成"1". 下例可以說明:

示例 #9 Unsetting $a

(refcount=1, is_ref=1)=array (   0 => (refcount=1, is_ref=0)='one',   1 => (refcount=1, is_ref=1)=...)

圖示:

清理變數容器的問題(Cleanup Problems)

儘管不再有某個作用域中的任何符號指向這個結構(就是變數容器),由於陣列元素“1”仍然指向陣列本身,所以這個容器不能被清除 。因為沒有另外的符號指向它,使用者沒有辦法清除這個結構,結果就會導致記憶體洩漏。慶幸的是,php將在指令碼執行結束時清除這個資料結構,但是在php清除之前,將耗費不少記憶體。如果你要實現分析演算法,或者要做其他像一個子元素指向它的父元素這樣的事情,這種情況就會經常發生。當然,同樣的情況也會發生在物件上,實際上物件更有可能出現這種情況,因為物件總是隱式的被引用。

如果上面的情況發生僅僅一兩次倒沒什麼,但是如果出現幾千次,甚至幾十萬次的記憶體洩漏,這顯然是個大問題。這樣的問題往往發生在長時間執行的指令碼中,比如請求基本上不會結束的守護程序(deamons)或者單元測試中的大的套件(sets)中。後者的例子:在給巨大的eZ(一個知名的PHP Library) 元件庫的模板元件做單元測試時,就可能會出現問題。有時測試可能需要耗用2GB的記憶體,而測試伺服器很可能沒有這麼大的記憶體。

文章引用:php.net官網

https://www.php.net/manual/zh/features.gc.refcounting-basics.php

10
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Android設計模式-21-享元模式