لقد أدت آلية جمع البيانات المهملة لمنصة Java إلى تحسين كفاءة المطور بشكل كبير، ولكن أداة تجميع البيانات المهملة سيئة التنفيذ قد تستهلك الكثير من موارد التطبيق. في الجزء الثالث من سلسلة تحسين أداء الجهاز الظاهري Java، تقدم Eva Andreasson للمبتدئين في Java نموذج الذاكرة وآلية جمع البيانات المهملة لمنصة Java. تشرح لماذا التجزئة (وليس جمع البيانات المهملة) هي المشكلة الرئيسية في أداء تطبيق Java، ولماذا يعد تجميع البيانات المهملة وضغطها من الأجيال الطرق الرئيسية (ولكنها ليست الأكثر ابتكارًا) للتعامل مع تجزئة تطبيق Java.
الغرض من جمع البيانات المهملة (GC) هو تحرير الذاكرة التي تشغلها كائنات Java التي لم تعد يتم الرجوع إليها بواسطة أي كائنات نشطة، وهو الجزء الأساسي من آلية إدارة الذاكرة الديناميكية لجهاز Java الظاهري. أثناء دورة تجميع البيانات المهملة النموذجية، يتم الاحتفاظ بجميع الكائنات التي لا تزال مرجعية (وبالتالي يمكن الوصول إليها)، بينما يتم تحرير تلك التي لم يعد يتم الرجوع إليها ويتم استعادة المساحة التي تشغلها لتخصيصها لكائنات جديدة.
من أجل فهم آلية جمع البيانات المهملة وخوارزميات جمع البيانات المهملة المختلفة، عليك أولاً معرفة شيء ما عن نموذج ذاكرة منصة Java.
جمع البيانات المهملة ونموذج ذاكرة منصة Java
عند بدء تشغيل برنامج Java من سطر الأوامر وتحديد معلمة بدء التشغيل -Xmx (على سبيل المثال: java -Xmx:2g MyApp)، يتم تخصيص ذاكرة بالحجم المحدد لعملية Java، وهو ما يسمى Java heap . تُستخدم مساحة عنوان الذاكرة المخصصة هذه لتخزين الكائنات التي تم إنشاؤها بواسطة برامج Java (وأحيانًا JVM). أثناء تشغيل التطبيق وتخصيص الذاكرة للكائنات الجديدة بشكل مستمر، ستمتلئ كومة Java (أي مساحة عنوان الذاكرة المخصصة) ببطء.
في النهاية، ستمتلئ كومة Java، مما يعني أن مؤشر ترابط تخصيص الذاكرة لا يمكنه العثور على مساحة متجاورة كبيرة بما يكفي لتخصيص ذاكرة للكائن الجديد. في هذا الوقت، يقرر JVM إخطار جامع البيانات المهملة وبدء جمع البيانات المهملة. يمكن أيضًا تشغيل عملية جمع البيانات المهملة عن طريق استدعاء System.gc() في البرنامج، ولكن استخدام System.gc() لا يضمن إجراء عملية جمع البيانات المهملة. قبل أي مجموعة من البيانات المهملة، ستحدد آلية جمع البيانات المهملة أولاً ما إذا كان إجراء عملية جمع البيانات المهملة آمنًا أم لا. عندما تكون جميع مؤشرات الترابط النشطة للتطبيق في نقطة آمنة، يمكن بدء عملية تجميع البيانات المهملة. على سبيل المثال، لا يمكن تنفيذ عملية جمع البيانات المهملة عندما يتم تخصيص الذاكرة لكائن ما، أو لا يمكن تنفيذ عملية جمع البيانات المهملة أثناء تحسين تعليمات وحدة المعالجة المركزية، لأنه من المحتمل فقدان السياق وستكون النتيجة النهائية غير صحيحة.
لا يمكن لمجمع البيانات المهملة استعادة أي كائن بمراجع نشطة، الأمر الذي قد يؤدي إلى كسر مواصفات جهاز Java الظاهري. ليست هناك حاجة لإعادة تدوير الكائنات الميتة على الفور، لأنه سيتم إعادة تدوير الكائنات الميتة في النهاية عن طريق جمع البيانات المهملة لاحقًا. على الرغم من وجود العديد من الطرق لتنفيذ جمع البيانات المهملة، إلا أن النقطتين المذكورتين أعلاه متماثلتان لجميع عمليات تنفيذ جمع البيانات المهملة. التحدي الحقيقي لجمع البيانات المهملة هو كيفية تحديد ما إذا كان الكائن حيًا وكيفية استعادة الذاكرة دون التأثير على التطبيق قدر الإمكان، لذلك فإن أداة تجميع البيانات المهملة لها الهدفان التاليان:
1. قم بتحرير الذاكرة غير المرجعية بسرعة لتلبية احتياجات تخصيص ذاكرة التطبيق لتجنب تجاوز سعة الذاكرة.
2. تقليل التأثير على تشغيل أداء التطبيق (زمن الوصول والإنتاجية) عند استعادة الذاكرة.
نوعان من جمع القمامة
في المقالة الأولى من هذه السلسلة، قدمت طريقتين لجمع البيانات المهملة، وهما عد المراجع وجمع التتبع. بعد ذلك، نستكشف هذين النهجين بشكل أكبر ونقدم بعض خوارزميات جمع التتبع المستخدمة في بيئات الإنتاج.
مرجع العد جامع
يقوم مجمع عد المرجع بتسجيل عدد المراجع التي تشير إلى كل كائن Java. بمجرد أن يصل عدد المراجع التي تشير إلى كائن إلى 0، يمكن إعادة تدوير الكائن على الفور. هذه السرعة هي الميزة الرئيسية للمجمع الذي يتم حسابه بالمرجع، ولا يوجد أي نفقات عامة تقريبًا في الحفاظ على الذاكرة التي لا يشير إليها أي مرجع، ولكن تتبع أحدث عدد مرجعي لكل كائن أمر مكلف.
تتمثل الصعوبة الرئيسية التي يواجهها جامع عد المراجع في كيفية التأكد من دقة عد المراجع. وهناك صعوبة أخرى معروفة وهي كيفية التعامل مع المراجع الدائرية. إذا كان هناك كائنان يشيران إلى بعضهما البعض ولم تتم الإشارة إليهما بواسطة كائنات حية أخرى، فلن تتم استعادة ذاكرة الكائنين أبدًا لأن عدد المراجع التي تشير إلى أي من الكائنين هو 0. تتطلب إعادة تدوير الذاكرة للهياكل المرجعية الدائرية تحليلًا كبيرًا (ملاحظة المترجم: التحليل الشامل لـ Java Heap)، مما سيزيد من تعقيد الخوارزمية وبالتالي يؤدي إلى زيادة الحمل على التطبيق.
جامع التتبع
يعتمد مجمع التتبع على افتراض أنه يمكن العثور على جميع الكائنات الحية من خلال تكرار المراجع (المراجع ومراجع المراجع) إلى مجموعة أولية معروفة من الكائنات الحية. يمكن تحديد المجموعة الأولية من الكائنات النشطة (وتسمى أيضًا الكائنات الجذرية) من خلال تحليل السجلات والكائنات العامة وإطارات المكدس. بعد تحديد المجموعة الأولية من الكائنات، يتبع جامع التتبع العلاقات المرجعية لهذه الكائنات ويضع علامة على الكائنات المشار إليها بواسطة المراجع ككائنات نشطة بالتسلسل، بحيث تستمر مجموعة الكائنات النشطة المعروفة في التوسع. تستمر هذه العملية حتى يتم وضع علامة على كافة الكائنات المشار إليها ككائنات حية، ويتم استعادة ذاكرة تلك الكائنات التي لم يتم وضع علامة عليها.
يختلف مجمع التتبع عن مجمع العد المرجعي بشكل رئيسي في أنه يمكنه التعامل مع الهياكل المرجعية الدائرية. يكتشف معظم جامعي التتبع الكائنات غير المرجعية في الهياكل المرجعية الدائرية أثناء مرحلة وضع العلامات.
يعد مُجمع التتبع هو أسلوب إدارة الذاكرة الأكثر استخدامًا في اللغات الديناميكية وهو حاليًا الأسلوب الأكثر شيوعًا في Java، كما تم التحقق منه في بيئات الإنتاج لسنوات عديدة. سأقدم أدناه أداة تجميع التتبع بدءًا من بعض الخوارزميات لتنفيذ عملية جمع التتبع.
خوارزمية جمع التتبع
إن نسخ جامعي البيانات المهملة وجامعي البيانات المهملة لا يعد أمرًا جديدًا، لكنهم ما زالوا الخوارزميتين الأكثر شيوعًا لتنفيذ مجموعات التتبع اليوم.
نسخ جامع القمامة
يستخدم جامع البيانات المهملة للنسخ التقليدي مساحتين للعناوين في الكومة (أي من الفضاء وإلى الفضاء). عند إجراء جمع البيانات المهملة، يتم نسخ الكائنات النشطة الموجودة في الفضاء إلى الفضاء تمت إزالتها (ملاحظة المترجم: بعد النسخ إلى المساحة أو الجيل القديم)، يمكن إعادة تدوير المساحة بالكامل عند تخصيص المساحة مرة أخرى، سيتم استخدام المساحة أولاً (ملاحظة المترجم: أي المساحة إلى سيتم استخدام الجولة السابقة كجولة جديدة من الفضاء).
في التنفيذ المبكر لهذه الخوارزمية، غيّر موقع "من الفضاء" و"إلى الفضاء" موقعهما بشكل مستمر، أي أنه عندما يمتلئ الفضاء "إلى" ويتم تشغيل مجموعة البيانات المهملة، يصبح "إلى الفضاء" هو "من الفضاء"، كما هو موضح في الشكل 1. .
الشكل 1: تسلسل جمع القمامة التقليدي
تسمح أحدث خوارزمية النسخ باستخدام أي مساحة عنوان في الكومة من حيث المساحة ومن الفضاء. بهذه الطريقة لا يحتاجون إلى تبديل المواضع مع بعضهم البعض، ولكن فقط يغيرون المواضع بشكل منطقي.
ميزة مجمع النسخ هي أن الكائنات المنسوخة في المساحة مرتبة بشكل مضغوط ولا يوجد تجزئة على الإطلاق. التجزئة هي مشكلة شائعة يواجهها جامعو القمامة الآخرون، وهي أيضًا المشكلة الرئيسية التي سأناقشها لاحقًا.
عيوب جامع النسخ
بشكل عام، جامع النسخ هو إيقاف العالم، مما يعني أنه طالما أن جمع البيانات المهملة قيد التقدم، لا يمكن تنفيذ التطبيق. مع هذا التنفيذ، كلما زاد عدد الأشياء التي تحتاج إلى نسخها، زاد التأثير على أداء التطبيق. يعد هذا عيبًا بالنسبة للتطبيقات الحساسة لوقت الاستجابة. عند استخدام أداة تجميع النسخ، تحتاج أيضًا إلى مراعاة السيناريو الأسوأ (أي أن جميع الكائنات الموجودة في الفضاء هي كائنات نشطة). يجب أن تكون المساحة كبيرة بما يكفي لتثبيت كافة الكائنات الموجودة في المساحة. نظرًا لهذا القيد، فإن استخدام الذاكرة لخوارزمية النسخ غير كافٍ قليلاً (ملاحظة المترجم: في أسوأ الحالات، يجب أن تكون المساحة إلى نفس حجم المساحة من، لذلك يتم استخدام 50٪ فقط).
جامع علامة واضحة
تستخدم معظم أجهزة JVM التجارية المنتشرة في بيئات إنتاج المؤسسات أداة تجميع العلامات (أو العلامات) لأنها لا تكرر تأثير أداة تجميع البيانات المهملة على أداء التطبيق. من أشهر جامعي العلامات CMS وG1 وGenPar وDeterministicGC.
يقوم مُجمِّع Mark-Sweep بتتبع مراجع الكائنات ووضع علامة على كل كائن تم العثور عليه على أنه حي باستخدام بت العلامة. تتوافق هذه العلامة عادةً مع عنوان أو مجموعة عناوين موجودة في الكومة. على سبيل المثال: يمكن أن تكون البتة النشطة بتة في رأس الكائن (ملاحظة المترجم: البتة) أو بت متجهة أو صورة نقطية.
بعد الانتهاء من وضع العلامات، يتم الدخول في مرحلة التنظيف. عادةً ما تعبر مرحلة التنظيف الكومة مرة أخرى (ليس فقط الكائنات التي تم وضع علامة عليها مباشرة، ولكن الكومة بأكملها) لتحديد مساحات عناوين الذاكرة المتجاورة غير المميزة (الذاكرة غير المميزة مجانية وقابلة لإعادة التدوير)، ثم يقوم المجمع بتنظيمها في قوائم مجانية. يمكن أن يحتوي جامع البيانات المهملة على قوائم مجانية متعددة (مقسمة عادةً وفقًا لحجم كتلة الذاكرة). حتى أن بعض جامعي JVM (على سبيل المثال: JRockit Real Time) يقومون بتقسيم القائمة المجانية ديناميكيًا بناءً على تحليل أداء التطبيق وإحصائيات حجم الكائن.
بعد مرحلة التنظيف، يمكن للتطبيق تخصيص الذاكرة مرة أخرى. عند تخصيص الذاكرة لكائن جديد من القائمة المجانية، يجب أن تتناسب كتلة الذاكرة المخصصة حديثًا مع حجم الكائن الجديد، أو متوسط حجم الكائن في مؤشر الترابط، أو حجم TLAB للتطبيق. يساعد العثور على كتل الذاكرة ذات الحجم المناسب للكائنات الجديدة على تحسين الذاكرة وتقليل التجزئة.
مارك - مسح عيوب المجمع
يعتمد وقت تنفيذ مرحلة العلامة على عدد الكائنات النشطة في الكومة، بينما يعتمد وقت تنفيذ مرحلة التنظيف على حجم الكومة. لذلك، في المواقف التي يكون فيها إعداد الكومة كبيرًا ويوجد العديد من الكائنات النشطة في الكومة، سيكون لخوارزمية مسح العلامات وقت توقف معين.
بالنسبة للتطبيقات كثيفة الاستهلاك للذاكرة، يمكنك ضبط معلمات جمع البيانات المهملة لتناسب سيناريوهات التطبيق واحتياجاته المختلفة. في كثير من الحالات، يؤدي هذا التعديل على الأقل إلى تأجيل المخاطر التي تشكلها مرحلة العلامة/المسح إلى اتفاقية مستوى الخدمة (SLA) الخاصة بالتطبيق أو الخدمة (يشير SLA هنا إلى وقت الاستجابة الذي يحتاج التطبيق إلى تحقيقه). لكن الضبط فعال فقط بالنسبة لأحمال محددة ومعدلات تخصيص الذاكرة. تتطلب تغييرات التحميل أو التعديلات على التطبيق نفسه إعادة الضبط.
تنفيذ جامع مارك الاجتياح
هناك على الأقل طريقتان مثبتتان تجاريًا لتنفيذ عملية جمع البيانات المهملة. أحدهما عبارة عن مجموعة متوازية من البيانات المهملة والآخر عبارة عن مجموعة من البيانات المهملة المتزامنة (أو في معظم الأحيان متزامنة).
جامع الموازي
المجموعة المتوازية تعني أنه يتم استخدام الموارد بشكل متوازٍ بواسطة سلاسل عمليات جمع البيانات المهملة. معظم التطبيقات التجارية للمجموعة المتوازية هي جامعات توقف العالم، حيث يتم إيقاف جميع سلاسل التطبيقات مؤقتًا حتى اكتمال جمع البيانات المهملة. نظرًا لأن جامعي البيانات المهملة يمكنهم استخدام الموارد بكفاءة، فإنهم عادةً ما يكون أداؤهم أفضل في معايير الإنتاجية، مثل SPECjbb. إذا كانت الإنتاجية أمرًا بالغ الأهمية لتطبيقك، فإن أداة تجميع البيانات المهملة الموازية تعد خيارًا جيدًا.
التكلفة الرئيسية للتجميع المتوازي (خاصة لبيئات الإنتاج) هي أن سلاسل التطبيقات لا يمكن أن تعمل بشكل صحيح أثناء جمع البيانات المهملة، تمامًا مثل جامع النسخ. ولذلك، فإن استخدام المجمعات المتوازية سيكون له تأثير كبير على التطبيقات الحساسة لزمن الاستجابة. خاصة عندما يكون هناك العديد من بنيات الكائنات النشطة المعقدة في مساحة الكومة، هناك العديد من مراجع الكائنات التي تحتاج إلى تعقبها. (تذكر أن الوقت الذي يستغرقه مُجمع Mark-Sweep لاستعادة الذاكرة يعتمد على الوقت الذي يستغرقه لتتبع مجموعة الكائنات الحية بالإضافة إلى الوقت الذي يستغرقه اجتياز الكومة بأكملها) باستخدام النهج المتوازي، يتم إيقاف التطبيق مؤقتًا لمدة وقت جمع القمامة بأكمله.
جامع متزامن
تعد أدوات تجميع البيانات المهملة المتزامنة أكثر ملاءمة للتطبيقات الحساسة لوقت الاستجابة. التزامن يعني أن خيط جمع البيانات المهملة وخيط التطبيق يتم تنفيذهما بشكل متزامن. لا يمتلك مؤشر ترابط جمع البيانات المهملة جميع الموارد، لذلك يحتاج إلى تحديد متى يبدأ تجميع البيانات المهملة، مما يتيح وقتًا كافيًا لتتبع مجموعة الكائنات النشطة واستعادة الذاكرة قبل أن تفيض ذاكرة التطبيق. إذا لم تكتمل عملية جمع البيانات المهملة في الوقت المناسب، فسيتسبب التطبيق في حدوث خطأ تجاوز سعة الذاكرة. ومن ناحية أخرى، لا تريد أن تستغرق عملية جمع البيانات المهملة وقتًا طويلاً لأنها ستستهلك موارد التطبيق وتؤثر على الإنتاجية. يتطلب الحفاظ على هذا التوازن مهارة، لذلك يتم استخدام الاستدلال في تحديد متى يبدأ جمع البيانات المهملة ومتى يتم اختيار تحسينات جمع البيانات المهملة.
هناك صعوبة أخرى تتمثل في تحديد الوقت الآمن لتنفيذ بعض العمليات (العمليات التي تتطلب لقطة كومة كاملة ودقيقة)، مثل الحاجة إلى معرفة وقت اكتمال مرحلة العلامة حتى يمكن الدخول في مرحلة التنظيف. هذه ليست مشكلة بالنسبة للمجمع المتوازي الذي يوقف العالم، لأن العالم متوقف مؤقتًا بالفعل (ملاحظة المترجم: تم إيقاف مؤشر ترابط التطبيق مؤقتًا، ويحتكر مؤشر ترابط جمع البيانات المهملة الموارد). لكن بالنسبة للمجمعات المتزامنة، قد لا يكون من الآمن التحول من مرحلة وضع العلامات إلى مرحلة التنظيف على الفور. إذا قام مؤشر ترابط التطبيق بتعديل جزء من الذاكرة التي تم تتبعها ووضع علامة عليها بواسطة أداة تجميع البيانات المهملة، فقد يتم إنشاء مراجع جديدة غير مميزة. في بعض تطبيقات المجموعة المتزامنة، يمكن أن يتسبب ذلك في تعطل التطبيق في حلقة من التعليقات التوضيحية المتكررة لفترة طويلة دون التمكن من الحصول على ذاكرة خالية عندما يحتاج التطبيق إلى هذه الذاكرة.
نعلم من المناقشة حتى الآن أن هناك العديد من جامعي البيانات المهملة وخوارزميات جمع البيانات المهملة، كل منها مناسب لأنواع تطبيقات محددة وأحمال مختلفة. ليس فقط خوارزميات مختلفة، ولكن تطبيقات خوارزمية مختلفة. لذلك، من الأفضل فهم احتياجات التطبيق وخصائصه قبل تحديد أداة تجميع البيانات المهملة. بعد ذلك، سنقدم بعض المزالق في نموذج ذاكرة منصة Java. تشير المزالق هنا إلى بعض الافتراضات التي يميل مبرمجو Java إلى القيام بها في بيئة إنتاج متغيرة ديناميكيًا مما يجعل أداء التطبيق أسوأ.
لماذا لا يمكن للضبط أن يحل محل مجموعة البيانات المهملة
يعرف معظم مبرمجي Java أن هناك العديد من الخيارات لتحسين برامج Java. تتيح العديد من معلمات JVM الاختيارية ومجمع البيانات المهملة وضبط الأداء للمطورين قضاء الكثير من الوقت في ضبط الأداء الذي لا نهاية له. أدى هذا إلى استنتاج بعض الأشخاص أن جمع البيانات المهملة أمر سيء، وأن الضبط لجعل مجموعات البيانات المهملة تحدث بشكل أقل تكرارًا أو تدوم لفترة أقصر يعد حلاً جيدًا، ولكن هذا أمر محفوف بالمخاطر.
خذ بعين الاعتبار الضبط لتطبيق معين. تعتمد معظم معلمات الضبط (مثل معدل تخصيص الذاكرة وحجم الكائن ووقت الاستجابة) على معدل تخصيص ذاكرة التطبيق (ملاحظة المترجم: أو معلمات أخرى) بناءً على حجم بيانات الاختبار الحالي. وقد يؤدي في النهاية إلى النتيجتين التاليتين:
1. حالة الاستخدام التي تنجح في الاختبار تفشل في الإنتاج.
2. تتطلب التغييرات في حجم البيانات أو التغييرات في التطبيقات إعادة الضبط.
يعد الضبط أمرًا متكررًا، وقد يتطلب جامعو البيانات المهملة المتزامنون على وجه الخصوص الكثير من الضبط (خاصة في بيئة الإنتاج). هناك حاجة إلى الاستدلال لتلبية احتياجات التطبيق. من أجل مواجهة السيناريو الأسوأ، قد تكون نتيجة الضبط تكوينًا صارمًا للغاية، مما يؤدي أيضًا إلى إهدار الكثير من الموارد. نهج الضبط هذا هو مسعى خيالي. في الواقع، كلما قمت بتحسين أداة تجميع البيانات المهملة لتتناسب مع حمل معين، كلما ابتعدت عن الطبيعة الديناميكية لوقت تشغيل Java. بعد كل شيء، ما هو عدد التطبيقات التي تتمتع بحمل ثابت، وما مدى موثوقية الحمل الذي تتوقعه؟
لذا، إذا كنت لا تركز على الضبط، فما الذي يمكنك فعله لمنع أخطاء نفاد الذاكرة وتحسين أوقات الاستجابة؟ أول شيء هو العثور على العوامل الرئيسية التي تؤثر على أداء تطبيقات Java.
التجزئة
العامل الذي يؤثر على أداء تطبيق Java ليس أداة تجميع البيانات المهملة، بل التجزئة وكيفية معالجة أداة تجميع البيانات المهملة للتجزئة. ما يسمى بالتجزئة هو حالة توجد فيها مساحة حرة في مساحة الكومة، ولكن لا توجد مساحة ذاكرة متجاورة كبيرة بما يكفي لتخصيص الذاكرة للكائنات الجديدة. كما هو مذكور في المقالة الأولى، تجزئة الذاكرة هي إما TLAB من المساحة المتبقية في الكومة، أو المساحة التي تشغلها كائنات صغيرة يتم تحريرها بين الكائنات طويلة العمر.
وبمرور الوقت وأثناء تشغيل التطبيق، ينتشر هذا التجزئة عبر الكومة. في بعض الحالات، قد يكون استخدام المعلمات المضبوطة بشكل ثابت أسوأ لأنها تفشل في تلبية الاحتياجات الديناميكية للتطبيق. لا يمكن للتطبيقات الاستفادة بكفاءة من هذه المساحة المجزأة. سيؤدي الفشل في القيام بأي شيء إلى مجموعات متتالية من البيانات المهملة حيث يحاول مجمّع البيانات المهملة تحرير الذاكرة لتخصيصها للكائنات الجديدة. في أسوأ الحالات، حتى مجموعات البيانات المهملة المتعاقبة لا يمكنها تحرير المزيد من الذاكرة (الكثير من التجزئة)، ومن ثم يتعين على JVM أن يلقي خطأ تجاوز سعة الذاكرة. يمكنك حل التجزئة عن طريق إعادة تشغيل التطبيق بحيث تحتوي كومة Java على مساحة ذاكرة متجاورة لتخصيص كائنات جديدة. تؤدي إعادة تشغيل البرنامج إلى التوقف عن العمل، وبعد فترة من الوقت، ستصبح كومة Java ممتلئة بالأجزاء مرة أخرى، مما يفرض إعادة تشغيل أخرى.
تشير أخطاء نفاد الذاكرة التي تؤدي إلى تعليق العملية والسجلات التي توضح أن أداة تجميع مجمعي البيانات المهملة محملة بشكل زائد إلى أن مجموعة البيانات المهملة تحاول تحرير الذاكرة وأن الكومة مجزأة بشكل كبير. سيحاول بعض المبرمجين حل مشكلة التجزئة عن طريق تحسين أداة تجميع البيانات المهملة مرة أخرى. لكنني أعتقد أننا يجب أن نجد طرقًا أكثر ابتكارًا لحل هذه المشكلة. ستركز الأقسام التالية على حلين للتجزئة: جمع القمامة من الأجيال وضغطها.
جمع القمامة الأجيال
ربما تكون قد سمعت النظرية القائلة بأن معظم الكائنات في بيئة الإنتاج قصيرة العمر. جمع القمامة عبر الأجيال هو استراتيجية لجمع القمامة مستمدة من هذه النظرية. في جمع البيانات المهملة عبر الأجيال، نقوم بتقسيم الكومة إلى مساحات (أو أجيال) مختلفة، وتخزن كل مساحة كائنات من أعمار مختلفة. ما يسمى بعمر الكائن هو عدد دورات جمع البيانات المهملة التي نجا منها الكائن (أي، كم عمر الكائن). لا يزال يتم الرجوع إليه بعد دورات جمع البيانات المهملة).
عندما لا يكون هناك مساحة متبقية في الجيل الجديد لتخصيصها، سيتم نقل الكائنات النشطة في الجيل الجديد إلى الجيل القديم (عادةً ما يكون هناك جيلان فقط. ملاحظة المترجم: سيتم نقل الكائنات التي تستوفي عمرًا معينًا فقط إلى الجيل القديم) الجيل القديم). غالبًا ما يستخدم جمع البيانات المهملة للأجيال أداة تجميع نسخ أحادية الاتجاه. تستخدم بعض أجهزة JVM الحديثة أدوات تجميع متوازية في الجيل الجديد. بالطبع، يمكن تنفيذ خوارزميات مختلفة لجمع البيانات المهملة للجيل الجديد والجيل القديم. إذا كنت تستخدم جامعًا موازيًا أو جامعًا ناسخًا، فإن جامعك الصغير هو جامع توقف العالم (انظر الشرح السابق).
يتم تخصيص الجيل القديم للكائنات التي تم نقلها من الجيل الجديد. وقد تمت الإشارة إلى هذه الكائنات لفترة طويلة أو تمت الإشارة إليها بواسطة مجموعة معينة من الكائنات في الجيل الجديد. في بعض الأحيان، يتم تخصيص الأجسام الكبيرة مباشرة للجيل القديم لأن تكلفة نقل الأجسام الكبيرة مرتفعة نسبيًا.
تكنولوجيا جمع القمامة بين الأجيال
في جمع القمامة عبر الأجيال، يتم تشغيل جمع القمامة بشكل أقل في الجيل القديم وبشكل متكرر في الجيل الجديد، ونأمل أيضًا أن تكون دورة جمع القمامة في الجيل الجديد أقصر. وفي حالات نادرة، قد يتم جمع الجيل الشاب بشكل متكرر أكثر من الجيل القديم. يمكن أن يحدث هذا إذا جعلت جيل الشباب كبيرًا جدًا وبقيت معظم الكائنات الموجودة في تطبيقك لفترة طويلة. في هذه الحالة، إذا تم تعيين الجيل القديم صغيرًا جدًا بحيث لا يمكنه استيعاب جميع الكائنات طويلة العمر، فإن مجموعة البيانات المهملة للجيل القديم ستكافح أيضًا لتحرير مساحة للكائنات التي يتم نقلها إليها. ومع ذلك، بشكل عام، يمكن لجمع البيانات المهملة عبر الأجيال تمكين التطبيقات من تحقيق أداء أفضل.
ومن الفوائد الأخرى لتقسيم الجيل الجديد أنه يحل مشكلة التشرذم إلى حد ما، أو يؤجل السيناريو الأسوأ. قد تكون هذه الكائنات الصغيرة ذات فترات البقاء القصيرة قد تسببت في حدوث مشكلات تجزئة، ولكن يتم تنظيفها جميعًا في الجيل الجديد من مجموعة البيانات المهملة. نظرًا لأنه يتم تخصيص مساحة أكبر للكائنات طويلة العمر عند نقلها إلى الجيل القديم، فإن الجيل القديم يكون أيضًا أكثر إحكاما. بمرور الوقت (إذا كان تطبيقك يعمل لفترة كافية)، سيصبح الجيل القديم أيضًا مجزأً، مما يتطلب تشغيل مجموعة كاملة أو أكثر من مجموعات البيانات المهملة، وقد يؤدي JVM أيضًا إلى ظهور أخطاء نفاد الذاكرة. لكن إنشاء جيل جديد يؤجل السيناريو الأسوأ، وهو ما يكفي للعديد من التطبيقات. بالنسبة لمعظم التطبيقات، فإنه يقلل من تكرار عمليات جمع البيانات المهملة التي توقف العالم وفرصة حدوث أخطاء خارج الذاكرة.
تحسين جمع القمامة بين الأجيال
كما ذكرنا من قبل، فإن استخدام جمع القمامة من الأجيال يؤدي إلى أعمال ضبط متكررة، مثل ضبط حجم الجيل الشاب، ومعدل الترقية، وما إلى ذلك. لا يمكنني التأكيد على المفاضلة في وقت تشغيل تطبيق معين: يؤدي اختيار حجم ثابت إلى تحسين التطبيق، ولكنه يقلل أيضًا من قدرة أداة تجميع البيانات المهملة على التعامل مع التغييرات الديناميكية، والتي لا مفر منها.
المبدأ الأول للجيل الجديد هو زيادته قدر الإمكان مع ضمان وقت التأخير أثناء جمع القمامة لإيقاف العالم، وفي الوقت نفسه، حجز مساحة كافية في الكومة للكائنات الباقية على المدى الطويل. فيما يلي بعض العوامل الإضافية التي يجب مراعاتها عند ضبط أداة تجميع القمامة من الأجيال:
1. معظم الأجيال الجديدة عبارة عن جامعي قمامة يوقفون العالم، وكلما زاد إعداد الجيل الجديد، زاد وقت الإيقاف المؤقت المقابل. لذلك، بالنسبة للتطبيقات التي تتأثر بشكل كبير بأوقات التوقف المؤقت لجمع البيانات المهملة، فكر بعناية في حجم جيل الشباب.
2. يمكن استخدام خوارزميات مختلفة لجمع البيانات المهملة على أجيال مختلفة. على سبيل المثال، يتم استخدام جمع القمامة المتوازي في الجيل الشاب ويتم استخدام جمع القمامة المتزامن في الجيل القديم.
3. عندما يتبين أن الترقية المتكررة (ملاحظة المترجم: الانتقال من الجيل الجديد إلى الجيل القديم) تفشل، فهذا يعني أن هناك أجزاء كثيرة جدًا في الجيل القديم، مما يعني عدم وجود مساحة كافية في الجيل القديم لتخزين الأشياء المنقولة من الجيل الجديد. في هذه المرحلة يمكنك ضبط معدل الترقية (أي ضبط عمر الترقية)، أو التأكد من أن خوارزمية جمع البيانات المهملة في الجيل القديم تقوم بعملية الضغط (مناقشتها في الفقرة التالية) وضبط الضغط ليناسب حمل التطبيق . من الممكن أيضًا زيادة حجم الكومة وحجم كل جيل، ولكن هذا سيؤدي إلى تمديد وقت الإيقاف المؤقت للجيل القديم. اعلم أن التجزئة أمر لا مفر منه.
4. يعتبر جمع القمامة من الأجيال هو الأكثر ملاءمة لمثل هذه التطبيقات. حيث يتم إعادة تدوير العديد من الكائنات الصغيرة ذات فترات البقاء القصيرة في الجولة الأولى من دورة جمع البيانات المهملة. بالنسبة لمثل هذه التطبيقات، يمكن لجمع القمامة من الأجيال أن يقلل بشكل فعال من التجزئة ويؤخر تأثير التجزئة.
ضغط
على الرغم من أن تجميع البيانات المهملة عبر الأجيال يؤخر حدوث التجزئة وأخطاء نفاد الذاكرة، إلا أن الضغط هو الحل الحقيقي الوحيد لمشكلة التجزئة. الضغط عبارة عن إستراتيجية لجمع البيانات المهملة تقوم بتحرير كتل متجاورة من الذاكرة عن طريق تحريك الكائنات، وبالتالي تحرير مساحة كافية لإنشاء كائنات جديدة.
يعد نقل الكائنات وتحديث مراجع الكائنات بمثابة عمليات إيقاف عالمية ستؤدي إلى قدر معين من الاستهلاك (مع استثناء واحد، والذي سيتم مناقشته في المقالة التالية في هذه السلسلة). كلما زاد عدد الكائنات التي تبقى على قيد الحياة، زاد وقت التوقف الناتج عن الضغط. في المواقف التي تكون فيها المساحة المتبقية قليلة والتجزئة شديدة (عادةً لأن البرنامج يعمل لفترة طويلة)، قد يكون هناك توقف مؤقت لبضع ثوان في المناطق المضغوطة التي تحتوي على العديد من الكائنات الحية، وعندما يقترب من تجاوز سعة الذاكرة، يتم ضغط يمكن أن تستغرق الكومة بأكملها عشرات الثواني.
يعتمد وقت التوقف المؤقت للضغط على مقدار الذاكرة التي يجب نقلها وعدد المراجع التي تحتاج إلى تحديث. يوضح التحليل الإحصائي أنه كلما زاد حجم الكومة، زاد عدد الكائنات الحية التي تحتاج إلى نقلها وتحديث المراجع. يبلغ وقت الإيقاف المؤقت حوالي ثانية واحدة لكل 1 جيجابايت إلى 2 جيجابايت من الكائنات الحية المنقولة، وبالنسبة لكومة بحجم 4 جيجابايت، فمن المحتمل أن يكون هناك 25% من الكائنات الحية، لذلك سيكون هناك توقف مؤقت من حين لآخر لمدة ثانية واحدة تقريبًا.
جدار ذاكرة الضغط والتطبيق
يشير جدار ذاكرة التطبيق إلى حجم الكومة الذي يمكن ضبطه قبل التوقف المؤقت الناتج عن تجميع البيانات المهملة (على سبيل المثال: الضغط). اعتمادًا على النظام والتطبيق، تتراوح معظم جدران ذاكرة تطبيقات Java من 4 جيجابايت إلى 20 جيجابايت. ولهذا السبب يتم نشر معظم تطبيقات المؤسسات على عدة أجهزة JVM أصغر حجمًا بدلاً من عدد قليل من أجهزة JVM الأكبر حجمًا. لنفكر في هذا: كم عدد تصميمات ونشرات تطبيقات Java للمؤسسات الحديثة التي تم تحديدها من خلال قيود الضغط الخاصة بـ JVM. في هذه الحالة، من أجل تجاوز وقت التوقف المؤقت لإلغاء تجزئة الكومة، استقرينا على نشر مثيلات متعددة كانت إدارتها أكثر تكلفة. يعد هذا غريبًا بعض الشيء نظرًا لقدرات التخزين الكبيرة لأجهزة اليوم والحاجة إلى زيادة الذاكرة لتطبيقات Java على مستوى المؤسسات. لماذا يتم تعيين عدد قليل فقط من غيغابايت من الذاكرة لكل مثيل. سيؤدي الضغط المتزامن إلى تحطيم جدار الذاكرة، وهو موضوع مقالتي القادمة.
تلخيص
هذه المقالة عبارة عن مقالة تمهيدية حول جمع البيانات المهملة لمساعدتك على فهم مفاهيم وآليات جمع البيانات المهملة، ونأمل أن تحفزك على قراءة المزيد من المقالات ذات الصلة. العديد من الأشياء التي تمت مناقشتها هنا كانت موجودة منذ فترة طويلة، وسيتم تقديم بعض المفاهيم الجديدة في المقالة التالية. على سبيل المثال، يتم تنفيذ الضغط المتزامن حاليًا بواسطة Zing JVM من Azul. إنها تقنية ناشئة لجمع البيانات المهملة تحاول حتى إعادة تعريف نموذج ذاكرة Java، خاصة مع استمرار تحسن الذاكرة وقوة المعالجة اليوم.
فيما يلي بعض النقاط الأساسية حول جمع البيانات المهملة التي قمت بتلخيصها:
1. تتكيف خوارزميات وتطبيقات جمع البيانات المهملة المختلفة مع احتياجات التطبيقات المختلفة. يعد جامع البيانات المهملة التتبع هو جامع البيانات المهملة الأكثر استخدامًا في أجهزة Java الافتراضية التجارية.
2. تستخدم مجموعة البيانات المهملة المتوازية جميع الموارد بشكل متوازٍ عند إجراء عملية جمع البيانات المهملة. عادةً ما يكون جامع البيانات المهملة متوقفًا عن العالم، وبالتالي يتمتع بإنتاجية أعلى، ولكن يجب أن تنتظر سلاسل العمليات العاملة في التطبيق حتى يكتمل مؤشر ترابط جمع البيانات المهملة، وهو ما له تأثير معين على وقت استجابة التطبيق.
3. مجموعة البيانات المهملة المتزامنة: أثناء تنفيذ المجموعة، لا يزال مؤشر ترابط عامل التطبيق قيد التشغيل. يحتاج جامع البيانات المهملة المتزامن إلى إكمال جمع البيانات المهملة قبل أن يحتاج التطبيق إلى الذاكرة.
4. تساعد مجموعة البيانات المهملة للأجيال على تأخير التجزئة، ولكنها لا تستطيع إزالة التجزئة. تقوم مجموعة البيانات المهملة من الأجيال بتقسيم الكومة إلى مساحتين، مساحة واحدة للكائنات الجديدة والأخرى للكائنات القديمة. تعتبر مجموعة البيانات المهملة من الأجيال مناسبة للتطبيقات التي تحتوي على العديد من الكائنات الصغيرة التي لها عمر افتراضي قصير.
5. الضغط هو الطريقة الوحيدة لحل التجزئة. يقوم معظم مجمعي البيانات المهملة بإجراء الضغط بطريقة توقف العالم، فكلما زاد تشغيل البرنامج، زادت تعقيد مراجع الكائنات، وكلما زاد توزيع أحجام الكائنات بشكل غير متساو، مما سيؤدي إلى أوقات ضغط أطول. يؤثر حجم الكومة أيضًا على وقت الضغط، حيث قد يكون هناك المزيد من الكائنات الحية والمراجع التي تحتاج إلى التحديث.
6. يساعد الضبط على تأخير أخطاء تجاوز سعة الذاكرة. لكن نتيجة الضبط الزائد هي تكوين صارم. قبل البدء في الضبط من خلال أسلوب التجربة والخطأ، تأكد من أنك تفهم الحمل الواقع على بيئة الإنتاج الخاصة بك، وأنواع كائنات التطبيق الخاص بك، وخصائص مراجع الكائنات الخاصة بك. قد لا تتمكن التكوينات القاسية جدًا من التعامل مع الأحمال الديناميكية، لذا تأكد من فهم العواقب عند تعيين قيم غير ديناميكية.
المقالة التالية في هذه السلسلة هي: مناقشة متعمقة لخوارزمية جمع البيانات المهملة C4 (المجمع المتزامن للضغط المستمر)، لذا ترقبوا ذلك!
(ينتهي النص الكامل)