لماذا لا تتصل بشكل صريح بـ Finalize() أو تبدأ أداة تجميع البيانات المهملة؟

StackOverflow https://stackoverflow.com/questions/28949

  •  09-06-2019
  •  | 
  •  

سؤال

بعد القراءة هذا السؤال, ، تم تذكيري عندما تعلمت Java وطُلب مني عدم الاتصال بـ Finalize () أو تشغيل أداة تجميع البيانات المهملة أبدًا لأنها "صندوق أسود كبير لا داعي للقلق بشأنه أبدًا".هل يمكن لأي شخص أن يلخص السبب وراء ذلك في بضع جمل؟أنا متأكد من أنني أستطيع قراءة تقرير فني من شركة Sun حول هذا الموضوع، ولكن أعتقد أن الإجابة اللطيفة والقصيرة والبسيطة ستشبع فضولي.

هل كانت مفيدة؟

المحلول

الجواب القصير:تعد مجموعة Java المهملة أداة تم ضبطها بدقة شديدة.System.gc() عبارة عن مطرقة ثقيلة.

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

إن استدعاء System.gc() مباشرة، على الرغم من أنه غير مضمون من الناحية الفنية للقيام بأي شيء، إلا أنه من الناحية العملية سيؤدي إلى تشغيل مجموعة كومة كاملة باهظة الثمن وإيقاف العالم.هذا هو دائمًا ما يكون الشيء الخطأ الذي يجب القيام به.تعتقد أنك توفر الموارد، ولكنك في الواقع تهدرها دون سبب وجيه، مما يجبر Java على إعادة فحص جميع الكائنات الحية الخاصة بك "في حالة حدوث ذلك".

إذا كنت تواجه مشاكل مع توقف GC مؤقتًا أثناء اللحظات الحرجة، فمن الأفضل لك تكوين JVM لاستخدام أداة تجميع العلامات/المسح المتزامنة، والتي تم تصميمها خصيصًا لتقليل الوقت المستغرق في التوقف مؤقتًا، بدلاً من محاولة استخدام مطرقة ثقيلة لحل المشكلة وفقط كسرها أبعد من ذلك.

وثيقة الشمس التي كنت تفكر فيها موجودة هنا: Java SE 6 HotSpot™ ضبط تجميع البيانات المهملة للجهاز الظاهري

(شيء آخر قد لا تعرفه:يؤدي تطبيق طريقة Finalize() على الكائن الخاص بك إلى جعل عملية جمع البيانات المهملة أبطأ.أولا، سوف يستغرق اثنين يعمل GC لتجميع الكائن:أحدهما لتشغيل Finalize() والآخر للتأكد من عدم إحياء الكائن أثناء الإنهاء.ثانيًا، يجب أن يتم التعامل مع الكائنات ذات الأساليب النهائية () كحالات خاصة بواسطة GC لأنه يجب جمعها بشكل فردي، ولا يمكن التخلص منها بكميات كبيرة.)

نصائح أخرى

لا تهتم بالنهائيات.

التبديل إلى جمع القمامة المتزايد.

إذا كنت تريد مساعدة جامع البيانات المهملة، فقم بإلغاء الإشارات إلى الكائنات التي لم تعد بحاجة إليها.مسار أقل يجب اتباعه = قمامة بشكل أكثر وضوحًا.

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

وفي سياق ذي صلة جدًا، إذا كنت تستخدم التسلسل، وقمت بإجراء تسلسل للكائنات المؤقتة، فسوف تحتاج إلى مسح ذاكرة التخزين المؤقت للتسلسل، عن طريق استدعاء ObjectOutputStream.reset() وإلا فإن العملية الخاصة بك سوف تتسرب من الذاكرة وتموت في النهاية.الجانب السلبي هو أن الكائنات غير العابرة ستتم إعادة تسلسلها.يمكن أن يكون تسلسل كائنات النتائج المؤقتة أكثر فوضوية قليلاً مما قد تعتقد!

فكر في استخدام المراجع البسيطة.إذا كنت لا تعرف ما هي المراجع الإلكترونية، فاقرأ javadoc لـ java.lang.ref.SoftReference

ابتعد عن المراجع الوهمية والمراجع الضعيفة إلا إذا كنت متحمسًا حقًا.

أخيرًا، إذا كنت حقًا لا تستطيع تحمل استخدام GC، فاستخدم Realtime Java.

لا، أنا لا أمزح.

التنفيذ المرجعي مجاني للتنزيل ويعتبر كتاب Peter Dibbles من SUN قراءة جيدة حقًا.

فيما يتعلق بالنهائيات:

  1. فهي عديمة الفائدة عمليا.ليس من المضمون أن يتم استدعاؤهم في الوقت المناسب، أو في الواقع، على الإطلاق (إذا لم يتم تشغيل GC مطلقًا، فلن يتم أيضًا تشغيل أي من المتأهلين للنهائيات).هذا يعني أنه لا ينبغي عليك الاعتماد عليها بشكل عام.
  2. لا يُضمن أن تكون أدوات الإنهاء النهائية عاجزة.يولي جامع القمامة عناية كبيرة لضمان عدم الاتصال أبدًا finalize() أكثر من مرة على نفس الكائن.مع الكائنات المكتوبة جيدًا، لن يكون الأمر مهمًا، ولكن مع الكائنات المكتوبة بشكل سيئ، قد يؤدي استدعاء الإنهاء عدة مرات إلى حدوث مشكلات (على سبيل المثال.الإصدار المزدوج من الموارد الأصلية ...يتحطم).
  3. كل الكائن الذي يحتوي على finalize() يجب أن توفر الطريقة أيضًا أ close() (أو ما شابه ذلك).هذه هي الوظيفة التي يجب أن تتصل بها.على سبيل المثال، FileInputStream.close().ليس هناك سبب للاتصال finalize() عندما يكون لديك طريقة أكثر ملاءمة لذلك يكون المقصود أن يتم استدعاؤها بواسطتك.

بافتراض أن أدوات الإنهاء النهائية تشبه الاسم نفسه لـ .NET، فإنك تحتاج فقط إلى الاتصال بها عندما يكون لديك موارد مثل مقابض الملفات التي يمكن أن تتسرب.في أغلب الأحيان، لا تحتوي كائناتك على هذه المراجع، لذا لا يلزم استدعاؤها.

من السيء أن تحاول جمع القمامة لأنها ليست قمامتك حقًا.لقد طلبت من VM تخصيص بعض الذاكرة عند إنشاء الكائنات، ويقوم أداة تجميع البيانات المهملة بإخفاء معلومات حول تلك الكائنات.داخليًا، يقوم GC بإجراء تحسينات على عمليات تخصيص الذاكرة التي يقوم بها.عندما تحاول جمع القمامة يدويًا، ليس لديك أي معرفة بما يريد GC التمسك به والتخلص منه، فأنت فقط تجبره على ذلك.ونتيجة لذلك، فإنك تعبث بالحسابات الداخلية.

إذا كنت تعرف المزيد عما كان يحتفظ به GC داخليًا، فقد تتمكن من اتخاذ قرارات أكثر استنارة، ولكنك فاتتك فوائد GC.

المشكلة الحقيقية في إغلاق مقابض نظام التشغيل في وضع اللمسات الأخيرة هي أن عملية الإنهاء يتم تنفيذها بدون ترتيب مضمون.ولكن إذا كان لديك مقابض للأشياء التي تمنعك (فكر على سبيل المثال:مآخذ التوصيل) من المحتمل أن يصل الكود الخاص بك إلى حالة توقف تام (ليس بالأمر التافه على الإطلاق).

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

SomeStream s = null;
...
try{
   s = openStream();
   ....
   s.io();
   ...
} finally {
   if (s != null) {
       s.close();
       s = null;
   }
}

يصبح الأمر أكثر تعقيدًا إذا قمت بكتابة فصولك الخاصة التي تعمل عبر JNI والمقابض المفتوحة.تحتاج إلى التأكد من إغلاق (تحرير) المقابض وأن ذلك سيحدث مرة واحدة فقط.يتم تجاهل مؤشر نظام التشغيل بشكل متكرر في Desktop J2SE Graphics[2D].حتى BufferedImage.getGrpahics() من المحتمل أن يعيد لك المقبض الذي يشير إلى برنامج تشغيل الفيديو (الذي يحتفظ في الواقع بالمورد على وحدة معالجة الرسومات).إذا لم تقم بتحريره بنفسك وتركه لأداة تجميع البيانات المهملة للقيام بالعمل - فقد تجد OutOfMemory غريبًا وموقفًا مشابهًا عندما نفدت الصور النقطية المعينة لبطاقة الفيديو ولكن لا يزال لديك الكثير من الذاكرة.في تجربتي، يحدث ذلك بشكل متكرر في حلقات ضيقة من العمل مع الكائنات الرسومية (استخراج الصور المصغرة، والقياس، وزيادة الوضوح، سمها ما شئت).

في الأساس، لا يعتني GC بمسؤولية المبرمجين عن الإدارة الصحيحة للموارد.إنه يهتم فقط بالذاكرة ولا شيء غير ذلك.من الأفضل تنفيذ استدعاء Stream.finalize Close() IMHO من خلال طرح استثناء RuntimeError الجديد ("تجميع البيانات المهملة للتدفق الذي لا يزال مفتوحًا").سيوفر ساعات وأيامًا من تصحيح الأخطاء وتنظيف التعليمات البرمجية بعد أن ترك الهواة القذرون النهايات خاسرة.

ترميز سعيد.

سلام.

يقوم GC بالكثير من التحسين بشأن وقت إنهاء الأمور بشكل صحيح.

لذا، ما لم تكن على دراية بكيفية عمل GC فعليًا وكيفية وضع علامات على الأجيال، فمن المحتمل أن يؤدي الاتصال اليدوي بإنهاء أو بدء GC إلى الإضرار بالأداء بدلاً من المساعدة.

تجنب اللمسات النهائية.ليس هناك ما يضمن أنه سيتم استدعاؤهم في الوقت المناسب.قد يستغرق الأمر وقتًا طويلاً قبل أن يقرر نظام إدارة الذاكرة (أي مجمع البيانات المهملة) جمع كائن باستخدام أداة الإنهاء.

يستخدم العديد من الأشخاص أدوات الإنهاء للقيام بأشياء مثل إغلاق اتصالات المقبس أو حذف الملفات المؤقتة.من خلال القيام بذلك، فإنك تجعل سلوك تطبيقك غير متوقع ومرتبطًا بوقت انتقال JVM إلى GC الكائن الخاص بك.يمكن أن يؤدي هذا إلى سيناريوهات "نفاد الذاكرة"، ليس بسبب استنفاد Java Heap، ولكن بسبب نفاد مقابض النظام لمورد معين.

شيء آخر يجب أخذه بعين الاعتبار هو أن تقديم الاستدعاءات إلى System.gc() أو مثل هذه المطارق قد يُظهر نتائج جيدة في بيئتك، لكنها لن تُترجم بالضرورة إلى أنظمة أخرى.لا يقوم الجميع بتشغيل نفس JVM، فهناك الكثير منها، SUN وIBM J9 وBEA JRockit وHarmony وOpenJDK وما إلى ذلك...تتوافق جميع أجهزة JVM هذه مع JCK (تلك التي تم اختبارها رسميًا)، ولكنها تتمتع بقدر كبير من الحرية عندما يتعلق الأمر بجعل الأمور سريعة.GC هي واحدة من تلك المجالات التي يستثمر فيها الجميع بكثافة.إن استخدام المطرقة سيؤدي في كثير من الأحيان إلى تدمير هذا الجهد.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top