أساسيات البرمجة الكائنيّة

دورة حياة الكائن وجمع البيانات غير المستخدمة

15 دقيقة الدرس 7 من 14

دورة حياة الكائن وجمع البيانات غير المستخدمة

كل كائن في Java يُولد، ويعيش في الذاكرة لفترة ما، ثم يموت في نهاية المطاف. خلافًا للغات مثل C و C++، لا تضطر أبدًا إلى تحرير الذاكرة يدويًا في Java — فـمجمّع البيانات غير المستخدمة (Garbage Collector — GC) يتولّى ذلك تلقائيًا. فهم متى ولماذا يُزال كائن ما يساعدك على كتابة كود صحيح وكفؤ في استخدام الذاكرة.

الخطوة الأولى — إنشاء الكائن

يُنشأ الكائن باستخدام الكلمة المحجوزة new. تقوم Java بشيئين في تلك اللحظة: تخصّص قطعة من الذاكرة في الكومة (Heap) لاستيعاب حقول الكائن، ثم تُشغّل الباني (Constructor) لتهيئة تلك الحقول.

public class Dog { String name; int age; Dog(String name, int age) { this.name = name; this.age = age; } } // داخل الدالة الرئيسية main: Dog rex = new Dog("Rex", 3); // يُنشأ الكائن في الكومة

بعد هذا السطر، rex هو متغير مرجعي (Reference Variable). لا يحتوي على الكائن نفسه، بل يحتوي على عنوان الذاكرة الذي يقطنه الكائن. فكّر فيه كجهاز تحكّم عن بُعد: جهاز التحكّم هو المتغير، والتلفاز هو الكائن في الكومة.

الخطوة الثانية — المراجع تُبقي الكائنات حيّة

يبقى الكائن حيًا طالما يوجد مرجع واحد على الأقل يشير إليه. يمكن أن يكون لديك مراجع متعددة للكائن نفسه:

Dog rex = new Dog("Rex", 3); Dog also = rex; // كلا المتغيّرَين يشيران إلى نفس الكائن also.age = 4; // يُعدّل الكائن المشترك System.out.println(rex.age); // يطبع 4 وليس 3
المراجع مقابل النسخ. إسناد متغير كائن إلى آخر لا ينسخ الكائن — بل ينسخ المرجع. يصبح كلا المتغيّرَين يشيران إلى نفس موقع الذاكرة في الكومة. هذا مصدر شائع جدًا للأخطاء لدى المبتدئين.

الخطوة الثالثة — الأهلية للجمع (GC)

يصبح الكائن مؤهلًا لجمع البيانات غير المستخدمة في اللحظة التي لا يشير إليه فيها أي مرجع يمكن الوصول إليه. هناك عدة طرق تحدث بها هذه الحالة:

  1. خروج المتغير من النطاق (Scope). تُدمَّر المتغيرات المحلية عند انتهاء الدالة التي تنتمي إليها.
  2. إعادة إسناد المتغير. يفقد الكائن القديم مرجعه.
  3. ضبط المتغير على null. يُزيل المرجع صراحةً.
// 1. الخروج من النطاق void createTemp() { Dog temp = new Dog("Buddy", 1); // temp يخرج من النطاق عند انتهاء الدالة // كائن Dog أصبح الآن مؤهلًا للجمع } // 2. إعادة الإسناد Dog a = new Dog("Luna", 2); a = new Dog("Max", 5); // كائن "Luna" لا مرجع له — مؤهل للجمع // 3. الإسناد إلى null Dog b = new Dog("Bella", 4); b = null; // كائن "Bella" أصبح الآن مؤهلًا للجمع
اضبط الكائنات الكبيرة على null حين تنتهي منها إذا كانت مخزّنة في متغيرات طويلة العمر (مثل الحقول الساكنة static أو حقول كائن طويل الأمد). أما المتغيرات المحلية فلا تحتاج لذلك لأنها تختفي تلقائيًا عند انتهاء الدالة.

الخطوة الرابعة — تشغيل مجمّع البيانات غير المستخدمة

كون الكائن مؤهلًا للجمع لا يعني تحرير ذاكرته فورًا. يعمل مجمّع البيانات غير المستخدمة التابع للـ JVM وفق جدولته الخاصة — عادةً حين تشحّ ذاكرة الكومة. يتتبّع جميع الكائنات التي يمكن الوصول إليها انطلاقًا من مجموعة مراجع جذرية (Root References) (المتغيرات المحلية في الخيوط الجارية، والحقول الساكنة) ويستردّ كل ما لا يمكن الوصول إليه.

يمكنك اقتراح تشغيل GC عبر System.gc()، لكن الـ JVM حرّ في تجاهل هذه الإشارة. لا تعتمد عليها أبدًا.

لا تكتب كودًا يعتمد على توقيت تشغيل GC. اللحظة الدقيقة لتحرير كائن ما غير محددة مسبقًا. إذا كان صفّك يحتفظ بمورد مثل ملف مفتوح أو اتصال بقاعدة بيانات، لا تعتمد على GC لإغلاقه — أغلقه صراحةً، ويفضّل استخدام كتلة try-with-resources.

جزيرة العزل (Island of Isolation)

يمكن للكائنات أن تشير إلى بعضها وتبقى غير قابلة للوصول من الكود الخارجي في آنٍ واحد. يُسمى هذا جزيرة العزل. يتعامل الـ GC الخاص بـ JVM مع هذا بشكل صحيح لأنه يعتمد على إمكانية الوصول من المراجع الجذرية، لا على عدّ المراجع فحسب.

class Node { Node next; } Node a = new Node(); Node b = new Node(); a.next = b; b.next = a; // كلٌّ من a و b يشير إلى الآخر a = null; b = null; // كلاهما الآن مؤهّل للجمع // حتى وإن كانا لا يزالان يشيران لبعضهما

دورة حياة الكائن — خلاصة

  • الإنشاء: تخصّص new ذاكرة في الكومة وتُشغّل الباني.
  • الاستخدام: مرجع واحد على الأقل قابل للوصول يُبقيه حيًا.
  • الأهلية للجمع: لا يوجد أي مرجع قابل للوصول.
  • الاسترداد: يُحرّر GC الذاكرة في الوقت الذي يراه مناسبًا.
لا يوجد free() يدوي في Java. صُمِّمت Java لكي يركّز المطوّرون على منطق الأعمال لا على إدارة الذاكرة. مجمّع البيانات متطوّر للغاية — المجمّعات الحديثة (G1، ZGC، Shenandoah) قادرة على استرداد الذاكرة بفترات توقف قصيرة جدًا حتى في التطبيقات الكبيرة.

الخلاصة العملية

في معظم كود Java اليومي لا تحتاج للتفكير في GC على الإطلاق. فقط دع الكائنات تخرج من نطاقها بصورة طبيعية. الحالات التي يهمّ فيها الأمر هي: الكائنات الكبيرة المحفوظة في متغيرات طويلة العمر، والمجموعات المخزّنة مؤقتًا التي تنمو بلا حدود، والصفوف التي تغلّف موارد أصيلة. في تلك الحالات، حرّر المراجع صراحةً واستخدم try-with-resources لكل ما يمكن إغلاقه.