Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Analyse der Garbage Collection und Speicherverwaltung in PHP

Detaillierte Analyse der Garbage Collection und Speicherverwaltung in PHP

不言
不言Original
2018-08-15 09:40:551243Durchsuche

Dieser Artikel bietet Ihnen eine detaillierte Analyse der Speicherbereinigung und Speicherverwaltung in PHP. Ich hoffe, dass er für Freunde hilfreich ist.

Referenzzählung

In PHP 5.2 und früheren Versionen verwendet die Garbage Collection von PHP den Referenzzählalgorithmus.

Grundkenntnisse der Referenzzählung

PHP-Variablen werden im Variablencontainer „zval“ (Datenstruktur) gespeichert. Das Attribut „zval“ enthält folgende Informationen:

  • Der Datentyp der aktuellen Variablen;

  • Der Wert der aktuellen Variablen; wird verwendet, um zu identifizieren, ob die Variable eine Referenz ist. Der übergebene boolesche Typbezeichner is_ref

  • zeigt auf den Refcount-Bezeichner der Anzahl der Variablen im Variablencontainer „zval“ (d. h. die Häufigkeit, mit der dieser zval referenziert wurde. Beachten Sie, dass sich die Referenz hier nicht auf einen Pass-Wert bezieht. Achten Sie auf die Unterscheidung.

  • Wenn einer Variablen ein Wert zugewiesen wird, wird ein entsprechender „zavl“-Variablencontainer generiert.

Informationen zum Variablen-Zval-Container anzeigen

Um die „zval“-Containerinformationen der Variablen anzuzeigen (d. h. is_ref und refcount der Variablen anzuzeigen), können Sie Folgendes tun: Sie können die Funktion xdebug_debug_zval() des XDebug-Debugging-Tools verwenden.

Informationen zur Installation des XDebug-Erweiterungs-Plug-Ins finden Sie in diesem Tutorial. Informationen zur Verwendung von XDebug finden Sie in der offiziellen Dokumentation. Angenommen, wir haben das XDebug-Tool erfolgreich installiert und können nun Variablen debuggen.

Zeigen Sie die ZVAL-Informationen gewöhnlicher Variablen an

  • Wenn unsere PHP-Anweisung nur eine einfache Zuweisung von Variablen ist, ist der is_ref-Bezeichnerwert 0. und der Refcount-Wert ist 1; wenn diese Variable als Wert einer anderen Variablen zugewiesen wird, wird der Refcount-Zähler des zval-Variablencontainers entsprechend erhöht. Wenn die Variable zerstört (nicht gesetzt) ​​wird, wird der „Refcount“ um 1 subtrahiert entsprechend.

  • Siehe das folgende Beispiel:
<?php // 变量赋值时,refcount 值等于 1
$name = &#39;liugongzi&#39;;
xdebug_debug_zval(&#39;name&#39;); // (refcount=1, is_ref=0)string &#39;liugongzi&#39; (length=9)

// $name 作为值赋值给另一个变量, refcount 值增加 1
$copy = $name;
xdebug_debug_zval(&#39;name&#39;); // (refcount=2, is_ref=0)string &#39;liugongzi&#39; (length=9)

// 销毁变量,refcount 值减掉 1
unset($copy);
xdebug_debug_zval(&#39;name&#39;); // (refcount=1, is_ref=0)string &#39;liugongzi&#39; (length=9)

Beim Schreiben kopieren

Beim Schreiben kopieren An Schreiben Sie: COW), eine einfache Beschreibung lautet: Wenn einer Variablen ein Wert durch Zuweisung zugewiesen wird, wird kein neuer Speicher zugewiesen, um den von der neuen Variablen gespeicherten Wert zu speichern, sondern der Speicher wird einfach nur über einen Zähler gemeinsam genutzt Wenn sich der Wert einer Variablen ändert, wird neuer Speicherplatz zugewiesen, um den Wertinhalt zu speichern und so die Speichernutzung zu reduzieren. - TPIP Copy-on-WriteAus den vorherigen Zval-Informationen zu einfachen Variablen wissen wir, dass $copy und $name den zval-Variablencontainer (Speicher) gemeinsam nutzen und dann refcount verwenden, um anzugeben, wie viele Variablen es gibt verwenden derzeit dieses zval.

Sehen Sie sich ein Beispiel an:

<?php $name = &#39;liugongzi&#39;;
xdebug_debug_zval(&#39;name&#39;); // name: (refcount=1, is_ref=0)string &#39;liugongzi&#39; (length=9)

$copy = $name;
xdebug_debug_zval(&#39;name&#39;); // name: (refcount=2, is_ref=0)string &#39;liugongzi&#39; (length=9)

// 将新的值赋值给变量 $copy
$copy = &#39;liugongzi handsome&#39;;
xdebug_debug_zval(&#39;name&#39;); // name: (refcount=1, is_ref=0)string &#39;liugongzi&#39; (length=9)
xdebug_debug_zval(&#39;copy&#39;); // copy: (refcount=1, is_ref=0)=&#39;liugongzi handsome&#39;

Ist Ihnen aufgefallen, dass die Refcount-Werte von Name und Kopie 1 werden, wenn der Variable $copy der Wert liugongzi gorgeous zugewiesen wird? In diesem Prozess werden die folgenden Vorgänge ausgeführt:

Trennen Sie die $-Kopie vom zval (inneren Slave) des $-Namens (d. h. kopieren); 🎜>Subtrahieren Sie 1 vom Refcount von $ >Hier ist nur eine kurze Einführung in das Thema „Kopieren beim Schreiben“. Interessierte Freunde können die Referenzmaterialien am Ende des Artikels für tiefergehende Recherchen lesen.

  • Zeigen Sie die Zval-Informationen der als Referenz übergebenen Variablen an

  • Die Regeln für die „Referenzzählung“ der Referenzübergabe als Wert (&) sind die gleichen wie bei gewöhnlichen Zuweisungsanweisungen, außer dass der Wert des Bezeichners

    is_ref
  • 1

    ist, was angibt, dass es sich bei der Variablen um einen Referenzübergabetyp handelt.

  • Sehen wir uns nun ein Beispiel für Pass-by-Reference an:
<?php $age = &#39;liugongzi&#39;;
xdebug_debug_zval(&#39;age&#39;); // (refcount=1, is_ref=0)string &#39;liugongzi&#39; (length=9)

$copy = &$age;
xdebug_debug_zval(&#39;age&#39;); // (refcount=2, is_ref=1)string &#39;liugongzi&#39; (length=9)

unset($copy);
xdebug_debug_zval(&#39;age&#39;); // (refcount=1, is_ref=1)string &#39;liugongzi&#39; (length=9)

    Referenzzählung zusammengesetzter Typen
  • versus Bei Skalartypen (Ganzzahl, Gleitkomma, Boolescher Wert usw.) sind die Referenzzählregeln für Typen wie Arrays und Objekte etwas komplizierter.

Zur besseren Erklärung schauen wir uns zunächst das Referenzzählbeispiel eines Arrays an:

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

// a:
// (refcount=1, is_ref=0)
// array (size=2)
//  'meaning' => (refcount=1, is_ref=0)string 'life' (length=4)
//  'number' => (refcount=1, is_ref=0)int 42
Das obige Referenzzähldiagramm sieht wie folgt aus:

  • Aus der Abbildung geht hervor, dass die Referenzzählregeln zusammengesetzter Typen im Grunde die gleichen sind wie die Zählregeln von Skalaren. Für das angegebene Beispiel erstellt PHP drei zval-Variablencontainer, einen zum Speichern das Array selbst, die anderen beiden werden zum Speichern von Elementen im Array verwendet.

    Wenn Sie dem Array ein vorhandenes Element hinzufügen, wird dessen Referenzzähler-Refcount um 1 erhöht.
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
$a['life'] = $a['meaning'];
xdebug_debug_zval( 'a' );

// a:
// (refcount=1, is_ref=0)
// array (size=3)
//  'meaning' => (refcount=2, is_ref=0)string 'life' (length=4)
//  'number' => (refcount=0, is_ref=0)int 42
//  'life' => (refcount=2, is_ref=0)string 'life' (length=4)
Das grobe Diagramm sieht wie folgt aus:

Detaillierte Analyse der Garbage Collection und Speicherverwaltung in PHPSpeicherverlust

Obwohl die Referenzzählregeln zusammengesetzter Typen in etwa denen skalarer Typen entsprechen, kann es, wenn der Wert der Referenz die Variable selbst ist (d. h. zyklische Anwendung), zu Speicherverlusten kommen, wenn dies nicht der Fall ist richtig gehandhabt.

Sehen wir uns das folgende Beispiel für die Übergabe eines Arrays als Referenz an:

<?php // @link http://php.net/manual/zh/function.memory-get-usage.php#96280
function convert($size)
{
    $unit=array(&#39;b&#39;,&#39;kb&#39;,&#39;mb&#39;,&#39;gb&#39;,&#39;tb&#39;,&#39;pb&#39;);
    return @round($size/pow(1024,($i=floor(log($size,1024)))),2).&#39; &#39;.$unit[$i];
}

// 注意:有用的地方从这里开始
$memory = memory_get_usage();

$a = array( &#39;one&#39; );

// 引用自身(循环引用)
$a[] =&$a;

xdebug_debug_zval( &#39;a&#39; );

var_dump(convert(memory_get_usage() - $memory)); // 296 b

unset($a); // 删除变量 $a,由于 $a 中的元素引用了自身(循环引用)最终导致 $a 所使用的内存无法被回收

var_dump(convert(memory_get_usage() - $memory)); // 568 b

从内存占用结果上看,虽然我们执行了 unset($a) 方法来销毁 $a 数组,但内存并没有被回收,整个处理过程的示意图如下:

Detaillierte Analyse der Garbage Collection und Speicherverwaltung in PHP

可以看到对于这块内存,再也没有符合表(变量)指向了,所以 PHP 无法完成内存回收,官方给出的解释如下:

尽管不再有某个作用域中的任何符号指向这个结构 (就是变量容器),由于数组元素 “1” 仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。庆幸的是,php 将在脚本执行结束时清除这个数据结构,但是在 php 清除之前,将耗费不少内存。如果你要实现分析算法,或者要做其他像一个子元素指向它的父元素这样的事情,这种情况就会经常发生。当然,同样的情况也会发生在对象上,实际上对象更有可能出现这种情况,因为对象总是隐式的被引用。

简单来说就是「引用计数」算法无法检测并释放循环引用所使用的内存,最终导致内存泄露。

引用计数系统的同步周期回收

由于引用计数算法存在无法回收循环应用导致的内存泄露问题,在 PHP 5.3 之后对内存回收的实现做了优化,通过采用 引用计数系统的同步周期回收 算法实现内存管理。引用计数系统的同步周期回收算法是一个改良版本的引用计数算法,它在引用基础上做出了如下几个方面的增强:

  • 引入了可能根(possible root)的概念:通过引用计数相关学习,我们知道如果一个变量(zval)被引用,要么是被全局符号表中的符号引用(即变量),要么被复杂类型(如数组)的 zval 中的符号(数组的元素)引用,那么这个 zval 变量容器就是「可能根」。

  • 引入根缓冲区(root buffer)的概念:根缓冲区用于存放所有「可能根」,它是固定大小的,默认可存 10000 个可能根,如需修改可以通过修改 PHP 源码文件 Zend/zend_gc.c 中的常量 GC_ROOT_BUFFER_MAX_ENTRIES,再重新编译。

  • 回收周期:当缓冲区满时,对缓冲区中的所有可能根进行垃圾回收处理。

下图(来自 PHP 手册),展示了新的回收算法执行过程:

Detaillierte Analyse der Garbage Collection und Speicherverwaltung in PHP

引用计数系统的同步周期回收过程

  1. 缓冲区(紫色框部分,称为疑似垃圾),存储所有可能根(步骤 A);

  2. 采用深度优先算法遍历「根缓冲区」中所有的「可能根(即 zval 遍历容器)」,并对每个 zval 的 refcount 减 1,为了避免遍历时对同一个 zval 多次减 1(因为不同的根可能遍历到同一个 zval)将这个 zvel 标记为「已减」(步骤 B);

  3. 再次采用深度优先遍历算法遍历「可能根 zval」。当 zval 的 refcount 值不为 0 时,对其加 1,否则保持为 0。并请已遍历的 zval 变量容器标记为「已恢复」(即步骤 B 的逆运算)。那些 zval 的 refcount 值为 0 (蓝色框标记)的就是应该被回收的变量(步骤 C);

  4. 删除所有 refcount 为 0 的可能根(步骤 D)。

整个过程为:

采用深度优先算法执行:默认删除 > 模拟恢复 > 执行删除 达到内存回收的目的。

优化后的引用计数算法优势

  • 将内存泄露控制在阀值内,这个由缓存区实现,达到缓冲区大小执行新一轮垃圾回收;

  • 提升了垃圾回收性能,不是每次 refcount 减 1 都执行回收处理,而是等到根缓冲区满时才开始执行垃圾回收。

你可以从 PHP 手册 的回收周期 了解更多,也可以阅读文末给出的参考资料。

PHP 7 的内存管理

PHP 5 中 zval 实现上的主要问题:

  • zval 总是单独 从堆中分配内存;

  • zval 总是存储引用计数和循环回收 的信息,即使是整型(bool / null)这种可能并不需要此类信息的数据;

  • 在使用对象或者资源时,直接引用会导致两次计数;

  • Einige indirekte Zugriffe erfordern eine bessere Handhabung. Beispielsweise werden jetzt vier Zeiger indirekt verwendet, um auf in Variablen gespeicherte Objekte zuzugreifen (die Länge der Zeigerkette beträgt vier).

  • Direktes Zählen bedeutet, dass Werte nur zwischen anderen geteilt werden können zvals . Dies funktioniert nicht, wenn Sie eine Zeichenfolge zwischen einem ZVAL und einem Hashtable-Schlüssel teilen möchten (es sei denn, der Hashtable-Schlüssel ist auch ein ZVAL).

Anpassung der zval-Datenstrukturimplementierung in PHP 7:

Die grundlegendste Änderung besteht darin, dass der von zval benötigte Speicher nicht mehr allein vom Heap zugewiesen wird, und wird nicht mehr von zval zugewiesen, speichert den Referenzzähler.
Der Referenzzähler komplexer Datentypen (wie Zeichenfolgen, Arrays und Objekte) wird selbst gespeichert.

Vorteile dieser Implementierung:

  • Einfache Datentypen erfordern keine separate Speicherzuweisung und erfordern keine Zählung;

    Keine Doppelzählungen mehr. In einem Objekt ist nur die im Objekt selbst gespeicherte Anzahl gültig;
  • Da die Anzahl jetzt durch den Wert selbst gespeichert wird (PHP verfügt über einen zval-Variablencontainerspeicher), kann dies auch der Fall sein kombiniert mit Nicht-Zval-Strukturen Datenaustausch, z. B. zwischen Zval und Hashtabellenschlüssel
  • Die Anzahl der für den indirekten Zugriff erforderlichen Zeiger wird reduziert.
  • Empfohlene verwandte Artikel:

Speicherverwaltung und Garbage Collection von PHP-Skripten – Persönlicher Artikel Sifu


Lernen Sie den Garbage-Collection-Mechanismus und die Speicherverwaltung von Javascript mit me_javascript-Kenntnissen


Garbage-Collection Eine kurze Erklärung des PHP-Garbage-Collection-Mechanismus

Das obige ist der detaillierte Inhalt vonDetaillierte Analyse der Garbage Collection und Speicherverwaltung in PHP. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn