خوارزميات جمع المهملات والضبط
خوارزميات جمع المهملات والضبط
إن معرفة أيّ جامع مهملات يعمل ولماذا يتصرف بهذه الطريقة هو الفرق بين التخمين في أعلام JVM واتخاذ قرارات مدروسة وقابلة للقياس. يركّز هذا الدرس على جامعَي المهملات اللذَين ستقابلهما في الخدمات الإنتاجية الحديثة — G1GC وZGC — وأعلام تحديد حجم الذاكرة الأساسية التي تتحكّم في سلوكهما.
لماذا توجد خوارزميات مختلفة لجمع المهملات
كلّ جامع مهملات ينطوي على مقايضات هندسية عبر ثلاثة محاور:
- الإنتاجية (Throughput) — إجمالي العمل الذي يؤدّيه التطبيق في وحدة زمنية (توقّفات GC تُقلّص هذه القيمة).
- زمن الاستجابة (Latency) — أطول توقّف يمكن أن يواجهه طلب مستخدم واحد.
- البصمة الذاكرية (Footprint) — قدر الذاكرة الذي يستهلكه الجامع نفسه لهياكله البيانية.
لا يتفوّق أيّ جامع على الآخرين في المحاور الثلاثة معًا. يبدأ اختيار الجامع المناسب من معرفة متطلّبات الخدمة: هل هدف زمن الاستجابة عند الشريحة المئوية التاسعة والتسعين أهمّ من الإنتاجية الإجمالية؟
الجامع ذو الأولوية للمهملات (G1GC)
G1 هو الجامع الافتراضي منذ JDK 9. فكرته الجوهرية تقسيم الذاكرة الكاملة إلى عدد كبير من المناطق المتساوية (عادةً بين 1 ميغابايت و32 ميغابايت، يختارها JVM تلقائيًا). المناطق لا تُخصَّص للجيلين الشاب أو القديم بصفة دائمة؛ بل يُعيد الجامع تصنيفها ديناميكيًا.
كيف يعمل G1 على مستوى عالٍ
- مرحلة الجيل الشاب فقط — يُجري G1 وضع علامات متزامنًا (Concurrent Marking) أثناء عمل التطبيق. توقّفات GC الصغيرة (STW) القصيرة تُخلّي المناطق الشابة من الكائنات الحية وتنقلها إلى مناطق الناجين أو القديم.
- مرحلة التجميع المختلط — بعد أن يُحدّد وضع العلامات المناطق القديمة التي تحتوي على أكبر قدر من المهملات، يضمّ G1 مجموعة من تلك المناطق في عمليات التجميع اللاحقة ("mixed GC"). ويجمع المناطق الأكثر مهملًا أولًا — ومن هنا اسم Garbage-First.
- Full GC — آخر خيار يلجأ إليه الجامع، وهو تجميع كامل مضغوط. قبل JDK 10 كان أحادي الخيط، أما بعده فهو متوازٍ. يجب تجنّبه في الإنتاج.
-XX:MaxGCPauseMillis، الافتراضي 200 ميلي ثانية) عبر تعديل عدد المناطق التي يجمعها في كلّ دورة. هذا الهدف تلميح لا ضمان.
أعلام G1 الأساسية
سطر أوامر إنتاجي حقيقي قد يبدو كالتالي:
ZGC — جمع المهملات بزمن استجابة فائق الانخفاض
ZGC (جاهز للإنتاج منذ JDK 15، أُضيف ZGC الجيلي في JDK 21) مصمَّم لهدف رئيسي واحد: توقّفات STW دون ميلي ثانية بصرف النظر عن حجم الذاكرة. يُحقّق هذا عبر تقنيتَين متقدّمتَين:
- المؤشرات الملوّنة (Colored Pointers) — يُشفّر ZGC بيانات GC الوصفية (بتات العلامة، حالة الانتقال) مباشرةً داخل المرجع ذي 64 بت. هذا يتيح له القيام بعمل حاجز التحميل (load barrier) عند استخدام المرجع بدل مسح الذاكرة بأكملها أثناء التوقّف.
- الانتقال المتزامن — خلافًا لـ G1 الذي ينقل الكائنات أثناء التوقّف، ينقل ZGC الكائنات الحية أثناء عمل التطبيق مستعملًا حاجز تحميل ذاتي الشفاء يُعيد توجيه المراجع القديمة بشفافية.
-XX:+ZGenerational في JDK 21.
أعلام ZGC الأساسية
G1 مقابل ZGC: متى تختار أيّهما
| المعيار | G1GC | ZGC |
|---|---|---|
| هدف التوقّف | عشرات إلى مئات الميلي ثانية | دون ميلي ثانية |
| عبء الإنتاجية | منخفض (~5–10% مقارنةً بـ Parallel GC) | أعلى قليلًا (حواجز التحميل) |
| الحجم المثالي للذاكرة | 4 جيجابايت – 32 جيجابايت | أيّ حجم، حتى تيرابايتات |
| البصمة الذاكرية | أدنى | أعلى قليلًا (بيانات المؤشرات الملوّنة) |
| أدنى إصدار JDK | JDK 9 | JDK 15 للإنتاج؛ JDK 21 للوضع الجيلي |
| حالة الاستخدام النموذجية | خدمات الويب، المعالجة الدُّفعية، الخدمات المصغّرة | تداول فوري، ألعاب، واجهات برمجية حسّاسة للزمن |
أعلام تحديد حجم الذاكرة: أساس ضبط GC
قبل لمس أيّ علم خاص بالجامع، اضبط حجم الذاكرة بشكل صحيح. هذه الأعلام الثلاثة تنطبق على كلّ جامع:
-Xmx أعلى من ذاكرة RAM الفعلية المتاحة ناقصًا احتياجات نظام التشغيل والعمليات الأخرى. إذا اضطرّ JVM إلى استخدام الـ Swap، تصبح كلّ دورة GC بطيئة كارثيًا. قاعدة شائعة: اترك 1–2 جيجابايت على الأقل لنظام التشغيل والعبء الإضافي لـ JVM (خيوط، مخازن JIT، Metaspace، المخازن المباشرة) عند ضبط -Xmx.
تفعيل سجلّ GC (ضروري للضبط)
لا يمكنك ضبط ما لا تقيسه. فعّل دائمًا سجلّ GC في الإنتاج:
يكتب هذا سجلّات GC دوّارة مع طوابع زمنية. أرسلها إلى GCEasy (gceasy.io) أو GCViewer للتحليل البصري — ستجد فورًا توزيعات وقت التوقّف ومعدّلات التخصيص ومعرفة ما إذا كانت Full GC تحدث.
تهيئة عملية كنقطة بداية
قِس أوقات التوقّف من سجلّ GC بعد التشغيل تحت حمل واقعي قبل تغيير أيّ شيء آخر. اضبط علمًا واحدًا في المرة وأعِد القياس — ضبط GC تجريبيٌّ بطبيعته، وليس قائمة مراجعة.
الخلاصة
- G1GC: يعتمد المناطق وهدف وقت التوقّف، افتراضي منذ JDK 9، ممتاز للذاكرة من 4 إلى 32 جيجابايت.
- ZGC: انتقال متزامن بمؤشرات ملوّنة، توقّفات دون ميلي ثانية، الوضع الجيلي في JDK 21 يُحسّن الإنتاجية بشكل جذري.
- تحديد حجم الذاكرة: اضبط
-Xmsمساويًا لـ-Xmxفي الإنتاج، اترك هامشًا لنظام التشغيل، وحدّد سقفًا للـ Metaspace. - سجّل دائمًا مخرجات GC وحلّلها قبل ضبط الأعلام — قِس أولًا، ثم غيّر.