الذاكرة وجامع القمامة في Kotlin: كيف لا تهدر RAM ولا تفقد الأداء
\n\nهل تساءلت يومًا أين تختفي الذاكرة العشوائية لتطبيقك بلغة Kotlin؟ يبدو أن الكود مكتوب بشكل مثالي، و coroutines تطير، ولكن بعد ساعة من العمل يبدأ البرنامج في "البطء" أو يتعطل مع OutOfMemoryError. ألم مألوف؟ مرحبًا بك في نادي أولئك الذين يريدون فهم كيف يدير Kotlin الذاكرة ولماذا جامع القمامة ليس شرًا، بل هو أفضل صديق لك (إذا كنت تعرف كيف تتعامل معه).
في هذه المقالة، سنغوص في موضوع إدارة الذاكرة في Kotlin ونحلل كيف يعمل جامع القمامة في Kotlin على منصات مختلفة. ستتعرف على ما هو المكدس والكومة، وكيف تتجنب التسريبات، ولماذا يمكن لـ WeakReference أن ينقذ مشروعك. هيا بنا!
ما هي الذاكرة في Kotlin؟ المكدس، الكومة وأصدقاؤهم
\n\nKotlin هي لغة متعددة المنصات. يتم تجميعها إلى byte-code لـ JVM، أو إلى كود أصلي (عبر Kotlin/Native)، أو إلى JavaScript (Kotlin/JS). وفي كل منصة، تكون الذاكرة منظمة بشكل مختلف قليلاً، لكن الأساس واحد في كل مكان.
\n\nدعنا نتذكر الأساسيات. عندما يبدأ برنامجك، يخصص له نظام التشغيل منطقة ذاكرة. داخل هذه المنطقة، هناك منطقتان رئيسيتان:
\n\n- \n
- المكدس (Stack) — هنا تُخزَّن المتغيرات المحلية، والأنواع البدائية، والمراجع إلى الكائنات. يعمل مثل كومة من الأطباق: سريع، قابل للتنبؤ، لكن بحجم محدود. تجاوز حدود المكدس يؤدي إلى
StackOverflowError. \n - الكومة (Heap) — مزبلة لجميع الكائنات التي تنشئها عبر
newأو المُنشئ (val list = mutableListOf()). الكومة كبيرة، لكنها بطيئة، وفيها "يعيش" جامع القمامة في Kotlin. \n
على JVM، يعمل المكدس والكومة بشكل تقليدي. في Kotlin/Native، المكدس هو مكدس الخيط الأصلي، والكومة تُدار بواسطة جامع القمامة الخاص بها. في Kotlin/JS، يقع كل شيء على عاتق محرك JavaScript (V8، SpiderMonkey).
\n\nالنقطة الأساسية: المراجع إلى الكائنات تُخزَّن في المكدس، بينما الكائنات نفسها في الكومة. طالما أن هناك مرجعًا "حيًا" واحدًا على الأقل للكائن (من المكدس أو من كائن آخر)، لن يلمسه جامع القمامة.
\n\nجامع القمامة في Kotlin: كيف يعمل على JVM
\n\nعندما تكتب بلغة Kotlin على JVM (Android، خادم)، تحصل على قوة Java Virtual Machine Garbage Collector (GC). هذا ليس شيئًا خاصًا بـ Kotlin، لكن فهم آليته ضروري.
\n\nتستخدم JVM الحديثة G1GC (Garbage First) أو ZGC (في الإصدارات الجديدة). كيف يعمل هذا بشكل مبسط:
\n\n- \n
- وضع العلامات (Mark) — يقوم GC "بالتجول" عبر جميع الكائنات الحية، بدءًا من النقاط الجذرية (المكدس، الحقول الثابتة). كل ما يصل إليه هو حي. \n
- التنظيف (Sweep) — كل ما لم يتم وضع علامة عليه يُعتبر قمامة ويُحذف. \n
- الضغط (Compact) — يتم نقل الكائنات الحية لتجنب التجزئة. \n
في G1GC، الكومة مقسمة إلى مناطق. يقوم GC بجمع المناطق التي تحتوي على أكبر قدر من القمامة (ومن هنا جاء الاسم). هذا يقلل من فترات التوقف، لكنه لا يلغيها تمامًا.
\n\nفي Kotlin، يمكنك التأثير على جامع القمامة عبر علامات JVM (للخوادم) أو عبر GarbageCollectorMXBean (للمراقبة). لكن في أغلب الأحيان، تحتاج فقط إلى كتابة كود لا يعيق عمل GC.
مثال: كيف يرى GC الكود الخاص بك
\n\nfun processData() {\n val list = mutableListOf<String>()\n for (i in 1..100_000) {\n list.add("Element $i")\n }\n // هنا list حي حتى ينتهي الأسلوب\n println(list.size)\n // بعد الخروج من الأسلوب، يصبح list قمامة\n}\n\n\nبعد انتهاء processData()، يختفي المرجع إلى list من المكدس، ويصبح الكائن مرشحًا للتجميع. بسيط، أليس كذلك؟
خصائص Kotlin/Native: جامع القمامة بدون JVM
\n\nKo