واحدة من أكبر فوائد استخدام لغة البرمجة النصية هي أنه يمكنك الاستفادة من آلية جمع البيانات المهملة التلقائية (تحرير الذاكرة). لا تحتاج إلى إجراء أي معالجة لتحرير الذاكرة بعد استخدام المتغير، وستقوم PHP بذلك نيابةً عنك.
بالطبع، يمكننا استدعاء الدالة unset() لتحرير الذاكرة إذا أردنا ذلك، ولكن في العادة ليست هناك حاجة للقيام بذلك.
ومع ذلك، في PHP، هناك موقف واحد على الأقل حيث لن يتم تحرير الذاكرة تلقائيًا، حتى لو تم استدعاء unset() يدويًا. يمكن العثور على التفاصيل على: http://bugs.php.net/bug.php?id=33595 .
أعراض المشكلة: إذا كانت هناك علاقة مرجعية متبادلة بين كائنين، مثل "الكائن الأصلي والكائن التابع"، فإن استدعاء unset() على الكائن الأصلي لن يؤدي إلى تحرير الذاكرة التي تشير إلى الكائن الأصلي في الكائن الفرعي (حتى لو كان الأصل الكائن عبارة عن مجموعة من البيانات المهملة، ولا يعمل أيضًا).
مرتبك قليلا؟ دعونا نلقي نظرة على الكود التالي:
<?phpclass Foo {function __construct(){$this->bar = new Bar($this);}}class Bar {function __construct($foo = null){$this-> foo = $foo;}}while (true) {$foo = new Foo();unset($foo);echo number_format(memory_get_usage()) "n";}?> قم بتشغيل هذا الرمز وسترى استخدام الذاكرة يصبح أعلى وأعلى حتى يتم استخدامه.
...33,551,61633,551,97633,552,33633,552,696PHP خطأ فادح: تم استنفاد حجم الذاكرة المسموح به وهو 33554432 بايت (حاول تخصيص 16 بايت) في memleak.php على السطر 17 بالنسبة لمعظم مبرمجي PHP، هذا هو الوضع ليست مشكلة.
ولكن إذا كنت تستخدم الكثير من الكائنات التي تشير إلى بعضها البعض في تعليمات برمجية طويلة الأمد، خاصة إذا كانت الكائنات كبيرة نسبيًا، فسيتم استنفاد الذاكرة بسرعة.
يعد حل Userland مملًا وغير أنيق إلى حد ما، لكن الرابط bugs.php.net المذكور سابقًا يوفر الحل.
يستخدم هذا الحل طريقة التدمير قبل تحرير الكائن لتحقيق هذا الهدف. يمكن للأسلوب Destructor مسح كافة مراجع الكائنات الأصلية الداخلية، مما يعني إمكانية تحرير هذا الجزء من الذاكرة الذي قد يتجاوز السعة.
إليك رمز "بعد الإصلاح":
<?phpclass Foo {function __construct(){$this->bar = new Bar($this);}function __destruct(){unset($this->bar);}}class شريط {وظيفة __construct($foo = null){$this->foo = $foo;}} while (true) {$foo = new Foo();$foo->__destruct();unset($foo);echo number_format(memory_get_usage()) "n";}?>لاحظ طريقة Foo::__destruct() الجديدة واستدعاء $foo->__destruct() قبل تحرير الكائن. الآن يحل هذا الكود مشكلة زيادة استخدام الذاكرة، بحيث يمكن أن يعمل الكود بشكل جيد.
حل نواة PHP؟
لماذا يحدث تجاوز الذاكرة؟ أنا لست ماهرًا في أبحاث PHP kernel، ولكنني متأكد من أن هذه المشكلة مرتبطة بإحصاء المراجع.
لن يتم تقليل العدد المرجعي لـ $foo المشار إليه في $bar لأنه تم تحرير الكائن الأصلي $foo في هذا الوقت، تعتقد PHP أنك لا تزال بحاجة إلى الكائن $foo، لذلك لن يتم تحرير هذا الجزء من الذاكرة. أو نحو ذلك.
يظهر جهلي هنا حقًا، لكن الفكرة العامة هي: لا يتم تقليل عدد المرجع، لذلك لا يتم تحرير بعض الذاكرة أبدًا.
في الرابط bugs.php.net المذكور أعلاه، رأيت أن تعديل عملية جمع البيانات المهملة من شأنه أن يضحي بالأداء الهائل، وبما أنني لا أعرف الكثير عن حساب المراجع، فقد افترضت أن هذا صحيح.
بدلاً من تغيير عملية جمع البيانات المهملة، لماذا لا يتم إلغاء تعيين () لتحرير الكائنات الداخلية؟ (أو اتصل بـ __destruct() عند تحرير الكائن؟)
ربما يستطيع مطورو PHP kernel إجراء تغييرات على آلية جمع البيانات المهملة هنا أو في أي مكان آخر.
تحديث: ذكر Martin Fjordvald في التعليقات رقعة كتبها David Wang لجمع القمامة (في الواقع تبدو أشبه بـ "قطعة قماش كاملة" - ضخمة جدًا. راجع معلومات تصدير CVS في نهاية هذه الرسالة الإلكترونية للحصول على التفاصيل.) في الواقع موجود (بريد إلكتروني) وقد حظي باهتمام من أعضاء مجتمع تطوير PHP kernel. السؤال هو ما إذا كان ينبغي وضع هذا التصحيح في PHP5.3 ولم يتلق الكثير من الدعم. أعتقد أن الحل الوسط الجيد هو استدعاء الأسلوب __destruct() في الكائن الموجود في الدالة unset();