0Pricing
PHP Academy · درس

إدارة الذاكرة وجمع البيانات المهملة

تعرّف إلى كيفية عمل zvals وrefcounting وGC

إدارة الذاكرة وجمع البيانات المهملة درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.

كيف تدير PHP الذاكرة

تعيش قيم PHP داخل zvals، وتُدار في الغالب باستخدام عدّ المراجع، مع وجود جامع للدورات المرجعية لاكتشاف ما لا يستطيع عدّ المراجع اكتشافه. يوضّح فهم ذلك تسريبات الذاكرة في العمال طويلي التشغيل، وتكلفة النسخ، ومتى تكون gc_collect_cycles() مهمة.

حاوية zval

إن zval حاوية لأي قيمة في PHP: وسم نوع بالإضافة إلى اتحاد لتخزين القيمة. تُخزَّن القيم البسيطة (int وfloat وbool) بالقيمة داخل zval، ولا تخضع لعدّ المراجع. أما الأنواع المعقدة (string وarray وobject) فتشير إلى بنية في الكومة تخضع لعدّ المراجع.

<?php
// debug_zval_refcount-style inspection via gc + count
$arr = [1, 2, 3];
$copy = $arr;   // refcount of the array bumps, no data copy yet (CoW)

// Modifying triggers copy-on-write separation
$copy[] = 4;

var_dump($arr);   // still [1,2,3]
var_dump($copy);  // [1,2,3,4]
?>

عدّ المراجع

تتتبّع كل قيمة تخضع لعدّ المراجع عدد قيم zval التي تشير إليها. يؤدي الإسناد إلى زيادة العدد، بينما يؤدي إلغاء الضبط أو مغادرة النطاق إلى إنقاصه. وعندما يصل العدد إلى الصفر، تُحرَّر الذاكرة فورًا، من دون انتظار دورة لجمع القمامة. وتُستعاد معظم ذاكرة PHP بهذه الطريقة.

<?php
$a = str_repeat('x', 1000);  // refcount 1 on the string
$b = $a;                      // refcount 2
unset($a);                    // refcount 1
unset($b);                    // refcount 0 -> freed instantly

echo "Refcounting frees deterministically\n";
?>

النسخ عند الكتابة

لا ينسخ الإسناد البيانات، بل يشاركها ويزيد عدد المراجع (النسخ عند الكتابة). ولا يحدث النسخ الفعلي إلا عند أول كتابة إلى قيمة مشتركة. وهذا يجعل تمرير المصفوفات الكبيرة قليل التكلفة إلى أن يجري تعديلها.

<?php
$big = range(1, 100000);
$shared = $big;          // O(1): shared, refcount++

// Reads stay shared and cheap
echo count($shared), PHP_EOL;

// First write separates (copies) — now it costs O(n)
$shared[0] = -1;
echo $big[0], ' vs ', $shared[0], PHP_EOL;  // 1 vs -1
?>

المشكلة: الدورات المرجعية

لا يستطيع عدّ المراجع وحده تحرير الدورات المرجعية. فإذا كان الكائن A يحتفظ بمرجع إلى B، وكان B يحتفظ بمرجع إلى A، فلن يصل عدّاداهما إلى الصفر حتى بعد إلغاء ضبط كل متغير خارجي؛ فتتسرّب الذاكرة إلى أن يعمل جامع الدورات.

<?php
class Node { public ?Node $ref = null; }

$a = new Node();
$b = new Node();
$a->ref = $b;   // a -> b
$b->ref = $a;   // b -> a  (cycle!)

unset($a, $b);  // external refs gone, but internal cycle keeps refcount > 0

// The objects are unreachable but NOT yet freed by refcounting alone.
echo gc_collect_cycles() . " cycles collected\n";
?>

جامع الدورات المرجعية

ينفّذ جامع القمامة في PHP وضع العلامات والمسح المتزامن انطلاقًا من الجذور. تُخزَّن جذور الدورات المحتملة مؤقتًا، وهي القيم التي انخفض عدّ مراجعها لكنه لم يصل إلى الصفر. وعندما يمتلئ مخزن الجذور (والقيمة الافتراضية 10,000)، يعمل جامع القمامة: يحاكي عمليات الإنقاص، ويعثر على المجموعات غير القابلة للوصول فعليًا، ثم يحررها.

<?php
// GC status exposes the root buffer and run stats
for ($i = 0; $i < 3; $i++) {
    $a = new stdClass();
    $b = new stdClass();
    $a->b = $b; $b->a = $a;   // create a cycle
    unset($a, $b);            // becomes a GC root
}

print_r(gc_status());
?>

التحكم في جامع القمامة

يمكنكم توجيه عمل الجامع: إذ تعمل gc_disable()/gc_enable() على تعطيله وتمكينه، بينما تفرض gc_collect_cycles() تنفيذ دورة وتعيد عدد العناصر التي جرى تحريرها. وفي المهام الدفعية التي تتطلب إنتاجية عالية، يعطّل البعض جامع القمامة أثناء مرحلة ضيقة، ثم ينفّذ الجمع مرة واحدة في النهاية.

<?php
gc_disable();              // no automatic cycle collection

// ... heavy phase that creates and drops many cycles ...
for ($i = 0; $i < 1000; $i++) {
    $x = new stdClass(); $y = new stdClass();
    $x->y = $y; $y->x = $x; unset($x, $y);
}

$freed = gc_collect_cycles();   // reclaim all at once
gc_enable();
echo "Freed $freed objects\n";
?>

العمال طويلي التشغيل

في خدمات CLI طويلة التشغيل، ومستهلكي قوائم الانتظار، وعمال Swoole/RoadRunner، لا تُستعاد الذاكرة عند إنهاء الطلب، لأن العملية تستمر لساعات. راقبوا ما يلي: ذاكرات التخزين المؤقت الثابتة التي تنمو بلا حدود، والدورات المتراكمة، والموارد غير المغلقة. راقبوا الاستخدام باستخدام memory_get_usage(true) وأعيدوا تشغيل العمال دوريًا.

<?php
$peakStart = memory_get_usage(true);

// Simulate processing a batch of jobs
for ($job = 0; $job < 5; $job++) {
    $payload = range(0, 10000);
    // ... process ...
    unset($payload);
}

printf("start: %d KB, peak: %d KB, now: %d KB\n",
    $peakStart >> 10,
    memory_get_peak_usage(true) >> 10,
    memory_get_usage(true) >> 10
);
?>

WeakReference وWeakMap

لتخزين البيانات مؤقتًا أو ربطها بكائن من دون إبقائه حيًا، استخدموا WeakReference (8.0) وWeakMap (8.0). لا تؤدي أي خانة في WeakMap إلى زيادة عدّ مراجع الكائن المفتاح، ولذلك يمكن جمع الكائن وإزالة خاناته تلقائيًا؛ وهذا مثالي لجداول البيانات الوصفية الجانبية التي يجب ألا تسبب تسريبًا.

<?php
$map = new WeakMap();

$obj = new stdClass();
$map[$obj] = 'metadata';      // does NOT keep $obj alive

echo count($map) . PHP_EOL;   // 1
unset($obj);                  // object collectible -> entry removed
echo count($map) . PHP_EOL;   // 0
?>

السلاسل الداخلية والقيم غير القابلة للتغيير

تكون السلاسل النصية الحرفية في مصدركم داخلية: تُخزَّن مرة واحدة، وتُشارك، ولا يُجرى عدّ مراجعها أو تحريرها أثناء الطلب. ينقل OPcache السلاسل الداخلية وop_arrays غير القابلة للتغيير (المخزّنة مؤقتًا) إلى الذاكرة المشتركة بين الطلبات. ولهذا تكون نسخ السلاسل الحرفية مجانية تقريبًا.

حد الذاكرة والمخصّص

تستخدم PHP مخصّص emalloc الخاص بها (مدير ذاكرة Zend) فوق مخصّص النظام، وتتتبّع الاستخدام لكل طلب مقارنةً بـmemory_limit. ويؤدي بلوغ الحد إلى حدوث خطأ فادح. وتعرض القيمة true في memory_get_usage(true) الكتل المخصّصة فعليًا من نظام التشغيل، لا ما يحتفظ به البرنامج النصي حاليًا فقط.

<?php
echo 'limit: ' . ini_get('memory_limit') . PHP_EOL;
echo 'emalloc usage: ' . memory_get_usage() . " bytes\n";   // ZendMM tracked
echo 'real usage:    ' . memory_get_usage(true) . " bytes\n"; // from OS
?>

تحقّق سريع

لماذا يمكن لعدّ المراجع وحده أن يسبب تسريب بعض الكائنات؟

مراجعة

تخزّن PHP القيم في zvals؛ فالقيم البسيطة تُخزَّن بالقيمة، بينما تُخصَّص الأنواع المعقدة في الكومة وتخضع لـعدّ المراجع مع النسخ عند الكتابة. يحرر عدّ المراجع الذاكرة بشكل حتمي عند وصول العدد إلى الصفر، لكنه لا يستطيع استعادة الدورات المرجعية؛ لذلك يعمل جامع متزامن لوضع العلامات والمسح عند امتلاء مخزن الجذور (أو عبر gc_collect_cycles()). استخدموا WeakMap/WeakReference للارتباطات غير المالكة، وراقبوا العمال طويلي التشغيل تحسبًا للنمو غير المحدود.

الأسئلة الشائعة

هل درس «إدارة الذاكرة وجمع البيانات المهملة» مجاني؟

نعم — نص درس «إدارة الذاكرة وجمع البيانات المهملة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.

ماذا ستتعلم في «إدارة الذاكرة وجمع البيانات المهملة»؟

تعرّف إلى كيفية عمل zvals وrefcounting وGC تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟

لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «إدارة الذاكرة وجمع البيانات المهملة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟

نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. كيف تعمل Zend Engine
  2. إدارة الذاكرة وجمع البيانات المهملة
  3. ترجمة OPcache وJIT
  4. كتابة إضافة PHP أساسية بلغة C
← العودة إلى PHP Academy