ملخص تسرب الذاكرة Android
الغرض من إدارة الذاكرة هو مساعدتنا على تجنب تسرب الذاكرة بشكل فعال في تطبيقاتنا أثناء التطوير. الجميع على دراية بتسربات الذاكرة. وببساطة ، فهذا يعني أن الكائن الذي يجب إصداره لم يتم إصداره ، وقد تم الاحتفاظ به بواسطة مثيل معين ولكن لم يعد يستخدم ، بحيث لا يمكن إعادة تدوير GC. لقد قرأت مؤخرًا الكثير من المستندات والمواد ذات الصلة. أخطط لتلخيصها وتسويتها ومشاركتها والتعلم معك ، وكذلك أعطي نفسي تحذيرًا حول كيفية تجنب هذه المواقف أثناء الترميز في المستقبل وتحسين تجربة التطبيق وجودة التطبيق.
سأبدأ بأساسيات تسرب ذاكرة Java ، وأستخدم أمثلة محددة لتوضيح الأسباب المختلفة لتسريبات ذاكرة Android ، وكذلك كيفية استخدام الأدوات لتحليل تسرب ذاكرة التطبيق ، وأخيراً تلخيصها.
استراتيجية تخصيص ذاكرة جافا
هناك ثلاثة أنواع من استراتيجيات تخصيص الذاكرة عند تشغيل برامج Java ، وهي تخصيص ثابت ، تخصيص المكدس ، وتخصيص الكومة. في المقابل ، فإن مساحة الذاكرة المستخدمة من قبل استراتيجيات التخزين الثلاث هي أساسا مساحة التخزين الثابتة (المعروفة أيضًا باسم منطقة الطريقة) ومنطقة المكدس ومنطقة الكومة.
منطقة التخزين الثابت (منطقة الطريقة): تخزن بشكل أساسي بيانات ثابتة وبيانات وثوابت ثابتة. يتم تخصيص قطعة الذاكرة هذه عند تجميع البرنامج ويوجد طوال فترة تشغيل البرنامج.
منطقة المكدس: عند تنفيذ طريقة ، يتم إنشاء متغيرات محلية في هيئة الطريقة (بما في ذلك نوع البيانات الأساسية ومرجع الكائن) على المكدس ، وسيتم إصدار الذاكرة التي تحتفظ بها هذه المتغيرات المحلية تلقائيًا في نهاية تنفيذ الطريقة. نظرًا لأن عملية تخصيص ذاكرة المكدس مدمجة في مجموعة تعليمات المعالج ، فهي فعالة للغاية ، لكن سعة الذاكرة المخصصة محدودة.
منطقة الكومة: المعروف أيضًا باسم تخصيص الذاكرة الديناميكية ، يشير عادة إلى الذاكرة الجديدة مباشرة عند تشغيل البرنامج ، أي مثيل للكائن. عندما لا يكون هذا الجزء من الذاكرة قيد الاستخدام ، سيكون جامع Java Garbage مسؤولًا عن إعادة التدوير.
الفرق بين المكدس والكومة:
يتم تخصيص بعض الأنواع الأساسية من المتغيرات المحددة في الطريقة التي يتم بها تخصيص متغيرات الجسم والمرجعية للكائنات في ذاكرة مكدس الطريقة. عندما يتم تعريف المتغير في كتلة من الطريقة ، ستخصص Java مساحة ذاكرة للمتغير على المكدس. عندما يتم تجاوز نطاق المتغير ، سيكون المتغير غير صالح ، وسيتم تحرير مساحة الذاكرة المخصصة له ، ويمكن إعادة استخدام مساحة الذاكرة.
يتم استخدام ذاكرة الكومة لتخزين جميع الكائنات التي تم إنشاؤها بواسطة جديد (بما في ذلك جميع متغيرات الأعضاء في الكائن) والصفائف. سيتم إدارة الذاكرة المخصصة في الكومة تلقائيًا بواسطة جامع القمامة Java. بعد إنشاء صفيف أو كائن في الكومة ، يمكن تحديد متغير خاص في المكدس. قيمة هذا المتغير تساوي العنوان الأول للمصفوفة أو الكائن في ذاكرة الكومة. هذا المتغير الخاص هو المتغير المرجعي الذي ذكرناه أعلاه. يمكننا الوصول إلى الكائنات أو المصفوفات في الكومة من خلال هذا المتغير المرجعي.
على سبيل المثال:
عينة الفئة العامة {int s1 = 0 ؛ عينة msample1 = new sample () ؛ public void method () {int s2 = 1 ؛ sample msample2 = new sample () ؛}} sample msample3 = new sample () ؛ يوجد المتغير المحلي S2 لفئة العينة والمتغير المرجعي MSAMPLE2 على المكدس ، ولكن الكائن الذي يشير إليه MSAMPLE2 موجود على الكومة.
يتم تخزين كيان الكائن الذي يشير إليه MSAMPLE3 على الكومة ، بما في ذلك جميع متغيرات الأعضاء S1 و MSAMPLE1 من هذا الكائن ، وهو موجود في المكدس.
ختاماً:
يتم تخزين أنواع البيانات الأساسية ومراجع المتغيرات المحلية في المكدس ، ويتم تخزين كيانات الكائن المشار إليها في الكومة. - لأنها تنتمي إلى المتغيرات في الأساليب ، تنتهي دورة الحياة بالطرق.
يتم تخزين جميع متغيرات الأعضاء وفي الكومة (بما في ذلك أنواع البيانات الأساسية ، وكيانات الكائنات المشار إليها والمحال بها) - لأنها تنتمي إلى فئات ، سيتم استخدام كائنات الفصل في النهاية للاستخدام الجديد.
بعد فهم تخصيص ذاكرة Java ، دعونا نلقي نظرة على كيفية إدارة Java للذاكرة.
كيف تدير جافا الذاكرة
إدارة ذاكرة Java هي مسألة تخصيص الكائنات وإصدارها. في Java ، يحتاج المبرمجون إلى التقدم بطلب للحصول على مساحة للذاكرة لكل كائن من خلال الكلمة الرئيسية الجديدة (باستثناء الأنواع الأساسية) ، وجميع الكائنات تخصص مساحة في الكومة (الكومة). بالإضافة إلى ذلك ، يتم تحديد إصدار الكائنات وتنفيذها بواسطة GC. في Java ، يتم تخصيص الذاكرة بواسطة البرامج ، بينما يتم إصدار الذاكرة بواسطة GC. هذه الطريقة من الإيرادات والنفقات على سطرين تبسط عمل المبرمجين. ولكن في الوقت نفسه ، فإنه يضيف أيضًا إلى عمل JVM. هذا أيضًا أحد الأسباب التي تجعل برامج Java أبطأ. لأنه من أجل إصدار الكائنات بشكل صحيح ، يجب على GC مراقبة حالة تشغيل كل كائن ، بما في ذلك التطبيق ، والاستشهاد ، والاستشهاد ، والتعيين ، وما إلى ذلك ، و GC يحتاج إلى مراقبته.
تتمثل مراقبة حالة الكائن في إصدار الكائن بشكل أكثر دقة وفي الوقت المناسب ، والمبدأ الأساسي لإطلاق الكائن هو أن الكائن لم يعد يشير إليه.
لفهم كيفية عمل GC بشكل أفضل ، يمكننا أن نعتبر الكائن قمة رسم بياني موجه ، والعلاقة المرجعية كحواف موجهة من الرسم البياني ، والتي من المرجع إلى الكائن المرجع. بالإضافة إلى ذلك ، يمكن استخدام كل كائن مؤشر ترابط كقمة البداية للرسم البياني. على سبيل المثال ، تبدأ معظم البرامج من العملية الرئيسية ، وبالتالي فإن الرسم البياني عبارة عن شجرة جذر تبدأ من قمة العملية الرئيسية. في هذا الرسم البياني الموجه ، فإن الكائنات التي يمكن الوصول إليها بواسطة قمة الجذر هي كائنات صالحة ، ولن تقوم GC بإعادة تدوير هذه الكائنات. إذا كان كائن (مخطط فرعي متصل) لا يمكن الوصول إليه من قمة الجذر هذه (لاحظ أن الرسم البياني عبارة عن رسم بياني موجه) ، فإننا نعتقد أن هذا الكائن (تلك) لم يعد يتم الرجوع إليه ويمكن إعادة تدويره بواسطة GC.
أدناه ، نقدم مثالاً على كيفية استخدام الرسوم البيانية الموجهة لتمثيل إدارة الذاكرة. في كل لحظة من البرنامج ، لدينا رسم بياني موجه يمثل تخصيص الذاكرة لـ JVM. الصورة أدناه هي رسم بياني للبرنامج على اليسار الذي يعمل إلى السطر 6.
تستخدم Java الرسوم البيانية الموجهة لإدارة الذاكرة ، والتي يمكن أن تقضي على مشكلة الحلقات المرجعية. على سبيل المثال ، هناك ثلاثة كائنات تشير إلى بعضها البعض. طالما أنها وعملية الجذر غير قابلة للوصول ، يمكن لـ GC أيضًا إعادة تدويرها. ميزة هذه الطريقة هي أن لديها دقة عالية في إدارة الذاكرة ، ولكنها منخفضة الكفاءة. هناك تقنية أخرى شائعة الاستخدام لإدارة الذاكرة هي استخدام العدادات. على سبيل المثال ، يستخدم نموذج COM طريقة العداد لإدارة المكونات. بالمقارنة مع الرسوم البيانية الموجهة ، فإنه يحتوي على خطوط دقة منخفضة (من الصعب التعامل مع المشكلات المرجعية الدائرية) ، ولكن لديها كفاءة تنفيذ عالية.
ما هو تسرب الذاكرة في جافا
في Java ، تسرب الذاكرة هي وجود بعض الكائنات المخصصة ، والتي لها خصائصتين التاليتين. أولاً ، يمكن الوصول إلى هذه الكائنات ، أي في الرسم البياني الموجه ، هناك مسارات يمكن توصيلها بها ؛ ثانياً ، هذه الكائنات عديمة الفائدة ، أي أن البرنامج لن يستخدم هذه الكائنات مرة أخرى في المستقبل. إذا كان الكائن يفي هاتين الشرطين ، فيمكن تحديد هذه الكائنات لتكون تسرب ذاكرة في Java ، ولن يتم إعادة تدوير هذه الكائنات بواسطة GC ، لكنها تحتل الذاكرة.
في C ++ ، تسرب الذاكرة لها نطاق أكبر. يتم تخصيص بعض الكائنات من مساحة الذاكرة ، ولكن بعد ذلك لا يمكن الوصول إليها. نظرًا لعدم وجود GC في C ++ ، لن يتم جمع هذه الذاكرة أبدًا. في Java ، يتم إعادة تدوير هذه الكائنات التي لا يمكن الوصول إليها بواسطة GC ، لذلك لا يحتاج المبرمجون إلى النظر في هذا الجزء من تسرب الذاكرة.
من خلال التحليل ، نعلم أنه بالنسبة لـ C ++ ، يحتاج المبرمجون إلى إدارة الحواف والقسمة بأنفسهم ، بينما بالنسبة لمبرمجي Java ، فإنهم بحاجة فقط إلى إدارة الحواف (لا حاجة لإدارة الرؤوس). وبهذه الطريقة ، تعمل جافا على تحسين كفاءة البرمجة.
لذلك ، من خلال التحليل أعلاه ، نعلم أن هناك أيضًا تسرب ذاكرة في Java ، ولكن النطاق أصغر من النطاق من C ++. نظرًا لأن لغة Java تضمن أنه يمكن الوصول إلى أي كائن ، يتم إدارة جميع الكائنات التي لا يمكن الوصول إليها بواسطة GC.
بالنسبة للمبرمجين ، فإن GC شفاف وغير مرئي بشكل أساسي. على الرغم من أن لدينا سوى عدد قليل من الوظائف للوصول إلى GC ، مثل System.gc () ، الذي يدير GC ، وفقًا لتعريف مواصفات لغة Java ، فإن هذه الوظيفة لا تضمن تنفيذ جامع القمامة JVM. لأنه قد يستخدم منفذ JVM المختلفون خوارزميات مختلفة لإدارة GC. بشكل عام ، يكون لخيوط GC أولوية أقل. هناك العديد من الاستراتيجيات لـ JVM للاتصال GC. يبدأ بعضهم فقط في العمل عندما يصل استخدام الذاكرة إلى مستوى معين. ينفذها البعض بانتظام. يقوم البعض بتنفيذ GC بسلاسة ، وبعض ينفذ GC بطريقة المقاطعة. لكن بشكل عام ، لا نحتاج إلى الاهتمام بهذا. ما لم يكن في بعض المواقف المحددة ، يؤثر تنفيذ GC على أداء التطبيق. على سبيل المثال ، بالنسبة للأنظمة المستندة إلى الويب في الوقت الفعلي مثل الألعاب عبر الإنترنت ، لا يرغب المستخدمون في مقاطعة تنفيذ التطبيق بشكل مفاجئ وأداء جمع القمامة ، ثم نحتاج إلى ضبط معلمات GC حتى يتمكن GC من تحرير الذاكرة بطريقة سلسة ، مثل تحلل جمع القمامة في سلسلة من الخطوات الصغيرة للتنفيذ. تدعم النقاط الساخنة JVM التي توفرها Sun هذه الميزة.
يعطي أيضًا مثالًا نموذجيًا على تسرب ذاكرة Java.
المتجه V = new Vector (10) ؛ for (int i = 1 ؛ i <100 ؛ i ++) {object o = new Object () ؛ v.add (o) ؛ o = null ؛ }في هذا المثال ، نتقدم بطلب للحصول على دورة كائن الكائن ونضع الكائن المطبق في متجه. إذا قامنا بإصدار المرجع نفسه فقط ، فلا يزال المتجه يشير إلى الكائن ، لذلك لا يمكن إعادة تدوير هذا الكائن لـ GC. لذلك ، إذا كان لا بد من حذف الكائن من المتجه بعد إضافته إلى المتجه ، فإن أسهل طريقة هي تعيين كائن المتجه على فارغ.
تسرب الذاكرة في جافا مفصلة
1. آلية إعادة تدوير ذاكرة جافا
بغض النظر عن طريقة تخصيص الذاكرة لأي لغة ، من الضروري إرجاع العنوان الحقيقي للذاكرة المخصصة ، أي إرجاع مؤشر إلى العنوان الأول من كتلة الذاكرة. يتم إنشاء الكائنات في Java باستخدام طرق جديدة أو انعكاس. يتم تخصيص إنشاء هذه الأشياء في الكومة. يتم جمع جميع الكائنات بواسطة الجهاز الظاهري Java من خلال آلية جمع القمامة. من أجل تحرير الكائنات بشكل صحيح ، ستقوم GC بمراقبة الحالة الصحية لكل كائن ومراقبة تطبيقها ، والاستشهاد ، والاستشهاد ، والتعيين ، وما إلى ذلك إذا لم يتم الوصول إليها ، فسيتم إعادة تدويرها ، والتي يمكن أن تقضي أيضًا على مشكلة الحلقات المرجعية. في لغة Java ، هناك نوعان من مساحة الذاكرة التي تحدد ما إذا كانت مساحة الذاكرة تلبي معايير جمع القمامة: أحدهما هو تعيين قيمة فارغة للكائن ، والتي لم يتم استدعاؤها أدناه ، والآخر هو تعيين قيمة جديدة للكائن ، وبالتالي إعادة تخصيص مساحة الذاكرة.
2. أسباب تسرب ذاكرة جافا
يشير تسرب الذاكرة إلى الكائن المستمر عديمة الفائدة (كائن لم يعد يستخدم) أو لا يمكن إصدار ذاكرة الكائنات غير المجدية في الوقت المناسب ، مما يؤدي إلى مضيعة مساحة الذاكرة ، والتي تسمى تسرب الذاكرة. في بعض الأحيان ليست تسرب الذاكرة خطيرة وليس من السهل اكتشافها ، لذلك لا يعرف المطورون أن هناك تسربًا للذاكرة ، ولكن في بعض الأحيان قد يكون الأمر خطيرًا للغاية وسيطالبك بالخروج من الذاكرة.
ما هو السبب الجذري لتسرب ذاكرة جافا؟ إذا كان كائن دورة الحياة الطويلة يحمل إشارة إلى كائن دورة قصيرة العمر ، فمن المحتمل أن يحدث تسرب الذاكرة. على الرغم من عدم الحاجة إلى كائن دورة العمر القصير ، إلا أنه لا يمكن إعادة تدويره لأنه يحمل مرجعه لدورة طويلة العمر. هذا هو السيناريو الذي تحدث فيه تسرب الذاكرة في جافا. هناك بشكل أساسي الفئات التالية:
1. فئة التجميع الثابتة تسبب تسرب الذاكرة:
من المرجح أن يحدث استخدام hashmap ، المتجه ، وما إلى ذلك في تسرب الذاكرة. تتوافق دورة حياة هذه المتغيرات الثابتة مع تلك الموجودة في التطبيق. لا يمكن إطلاق جميع الكائنات التي يشيرون إليها لأنه سيتم الرجوع إليها أيضًا بواسطة Vector ، إلخ.
على سبيل المثال
ناقل ثابت v = ناقل جديد (10) ؛ لـ (int i = 1 ؛ i <100 ؛ i ++) {object o = new Object () ؛ v.add (o) ؛ o = null ؛}في هذا المثال ، يتم تطبيق كائن كائن الحلقة ويتم وضع الكائن المطبق في متجه. إذا تم إصدار المرجع نفسه فقط (O = NULL) ، لا يزال المتجه يشير إلى الكائن ، لذلك لا يمكن إعادة تدوير هذا الكائن لـ GC. لذلك ، إذا كان لا بد من حذف الكائن من المتجه بعد إضافته إلى المتجه ، فإن أسهل طريقة هي تعيين كائن المتجه على فارغ.
2. عندما يتم تعديل خصائص الكائن في المجموعة ، لن تعمل طريقة REMOM ().
على سبيل المثال:
public static void main (string [] args) {set <Person> set = new hassset <Person> () ؛ person p1 = شخص جديد ("tang monk" ، "pwd1" ، 25) ؛ person p2 = شخص جديد ("Sun Wukong" ، "PWD2" ، 26) Bajie "،" pwd3 "، 27) ؛ set.add (p1) ؛ set.add (p2) ؛ set.add (p3) ؛ system.out.println (" هناك إجمالي: "+set.size ()+" عناصر! ") ؛ // النتيجة: هناك ما مجموعه: 3 عناصر! p3.setage (2) ؛ // تعديل عمر P3 ، وقيمة Hashcode المقابلة لتغييرات عنصر P3 في هذا الوقت set.remove (p3) ؛ // إزالته في هذا الوقت ، مما تسبب في مجموعة تسرب الذاكرة. add (p3) ؛ // أضفه مرة أخرى وتم إضافته بنجاح system.out.println ("هناك:"+set.size ()+"عناصر!") ؛ // النتيجة: هناك: 4 عناصر في المجموع! لـ (الشخص الشخص: تعيين) {system.out.println (person) ؛}}3. المستمع
في برمجة Java ، نحتاج جميعًا إلى التعامل مع المستمعين. عادة ، يتم استخدام الكثير من المستمعين في التطبيق. سوف ندعو طريقة تحكم مثل AddXXXListener () لإضافة مستمعين ، ولكن في كثير من الأحيان عند إطلاق الكائن ، لا نتذكر حذف هؤلاء المستمعين ، وبالتالي زيادة فرصة تسرب الذاكرة.
4. اتصالات مختلفة
على سبيل المثال ، لن يتم إعادة تدوير اتصال قاعدة البيانات (Datasourse.getConnection ()) واتصالات الشبكة (Socket) و IO تلقائيًا بواسطة GC ما لم يتصل بشكل صريح عن طريقة الإغلاق () لإغلاق اتصالها. لا يمكن إعادة تدوير كائنات ResultSet و Trufts بشكل صريح ، ولكن يجب إعادة تدوير الاتصال بشكل صريح لأنه لا يمكن إعادة تدوير الاتصال تلقائيًا في أي وقت. بمجرد إعادة تدوير الاتصال ، ستكون كائنات ResultSet و Trufts Null خالية على الفور. ومع ذلك ، إذا كنت تستخدم تجمع اتصال ، فإن الموقف مختلف. بالإضافة إلى إغلاق الاتصال بشكل صريح ، يجب عليك أيضًا إغلاق كائن بيان Results بشكل صريح (إغلاق أحدها ، سيتم إغلاق الآخر أيضًا) ، وإلا فلن يتم إصدار عدد كبير من كائنات العبارات ، مما يسبب تسرب الذاكرة. في هذه الحالة ، سيتم إصدار الاتصال عادة في المحاولة وأخيراً.
5. الإشارات إلى الفئات الداخلية والوحدات الخارجية
من السهل نسبيًا أن ننسى الإشارات إلى الفصول الداخلية ، وبمجرد عدم إصدارها ، قد لا يتم إصدار سلسلة من كائنات فئة الخلف. بالإضافة إلى ذلك ، يجب أن يكون المبرمجون حذرين أيضًا من الإشارات غير المقصودة إلى الوحدات الخارجية. على سبيل المثال ، المبرمج A مسؤول عن الوحدة A ويطلق على طريقة الوحدة B مثل:
سجل الفراغ العام (كائن ب) ؛
هذا النوع من المكالمة يتطلب رعاية كبيرة. عند تمرير كائن ما ، من المحتمل جدًا أن تحتفظ الوحدة B بالإشارة إلى الكائن. في هذا الوقت ، تحتاج إلى الانتباه إلى ما إذا كانت الوحدة B توفر العمليات المقابلة لإزالة المراجع.
6. وضع سينجلتون
يعد الاستخدام غير الصحيح لنمط Singleton مشكلة شائعة تسبب تسرب الذاكرة. ستكون كائنات Singleton موجودة طوال دورة حياة JVM بأكملها بعد التهيئة (في شكل متغيرات ثابتة). إذا كان كائن Singleton يحتوي على مراجع خارجية ، فلن يتم إعادة تدوير هذا الكائن بشكل طبيعي بواسطة JVM ، مما يؤدي إلى تسرب الذاكرة. النظر في المثال التالي:
الفئة A {public a () {B.GetInstance (). seta (this) ؛} ....} // class B تستخدم وضع Singleton Class B {private a ؛ private static b exture = new b () ؛ public b ()من الواضح أن B يتبنى نمط Singleton ، الذي يحمل إشارة إلى كائن A ، ولن يتم إعادة تدوير كائن هذه الفئة A. تخيل ما سيحدث إذا كان A كائنًا أو نوعًا أكثر تعقيدًا
ملخص لتسربات الذاكرة الشائعة في Android
مجموعة تسرب فئة
إذا كان لدى فئة التجميع طريقة فقط لإضافة عناصر وليس لديها آلية حذف مقابلة ، فسيتم احتلال الذاكرة. إذا كانت فئة المجموعة هذه متغيرًا عالميًا (مثل الخواص الثابتة في الفصل ، والخريطة العالمية ، وما إلى ذلك ، هناك مرجع ثابت أو يشير إلى ذلك طوال الوقت) ، فلا توجد آلية حذف مقابلة ، والتي قد تتسبب في زيادة الذاكرة التي تشغلها المجموعة فقط. على سبيل المثال ، المثال النموذجي أعلاه هو أحد هذه الحالات. بالطبع ، لن نكتب هذا الرمز 2B في المشروع ، لكن لا يزال من السهل الحدوث إذا لم نكن حريصين. على سبيل المثال ، نود جميعًا القيام ببعض ذاكرة التخزين المؤقت من خلال HashMap ، لذلك يجب أن نكون أكثر حذراً في هذا الموقف.
تسرب الذاكرة الناجم عن المفرد
نظرًا لأن الطبيعة الثابتة لـ Singleton تجعل دورة حياتها طالما أن دورة حياة التطبيق ، إذا تم استخدامها بشكل غير لائق ، فمن السهل التسبب في تسرب الذاكرة. على سبيل المثال ، المثال النموذجي التالي ،
appmanager الفئة العامة {مثيل appmanager الثابت الخاص ؛ سياق السياق الخاص ؛ private appmanager (سياق السياق) {this.context = context ؛} appmanager appmanager العامة (سياق السياق)هذا هو نمط Singleton طبيعي. عند إنشاء هذا المفرد ، نظرًا لأن السياق يحتاج إلى تمريره ، فإن طول دورة حياة هذا السياق أمر بالغ الأهمية:
1. إذا تم تمرير سياق التطبيق في هذا الوقت ، لأن دورة حياة التطبيق هي دورة حياة التطبيق بأكمله ، فلن تكون هناك مشكلة.
2. إذا تم تمرير سياق النشاط في هذا الوقت ، عندما يخرج النشاط المقابل لهذا السياق ، نظرًا لأن الإشارة إلى السياق يتم الاحتفاظ بها بواسطة كائن Singleton ، فإن دورة حياته مساوية لدورة حياة التطبيق بأكملها ، لذلك عندما يخرج النشاط ، لن يتم إعادة تدوير ذاكرته ، والتي تسبب تسربًا.
يجب تغيير الطريقة الصحيحة إلى ما يلي:
الفئة العامة appmanager {مثيل appmanager الثابت الخاص ؛ سياق السياق الخاص ؛ private appmanager (سياق السياق) {this.context = context.getapplicationContext () ؛ // context user}أو اكتب بهذه الطريقة ، ولا تحتاج حتى إلى تمرير السياق في:
إضافة طريقة ثابتة إلى التطبيق الخاص بك ، GetContext () إرجاع سياق التطبيق.
...
context = getapplicationContext () ؛ .../*** الحصول على السياق العالمي*return return context consult*/public static context getContext () {return context ؛} class public appmanager {private static appmanager مثيل appmanager ؛ sive appmanager الخاص () {this.context = myapplicalition.gettxt. getInstance () {if (مثيل == null) {مثيل = جديد appmanager () ؛} الإرجاع مثيل ؛}}فئات داخلية مجهولة/فئات داخلية غير متجانسة وخيوط غير متزامنة
تسرب الذاكرة الناجم عن إنشاء مثيلات ثابتة في الطبقات الداخلية غير الستاطية
في بعض الأحيان قد نبدأ الأنشطة بشكل متكرر. من أجل تجنب إنشاء موارد البيانات نفسها مرارًا وتكرارًا ، قد تحدث طريقة الكتابة هذه:
يمتد النشاط الرئيسي للطبقة العامة AppCompatActivity {private static testresource mresource = null ؛ overrideprotected void onCreate (bundle saveInstancestate) {super.oncreat testResource () ؛} // ...} TestResource {// ...}}هذا يخلق مفردة من فئة داخلية غير قاسية داخل النشاط ، ويتم استخدام بيانات المفرد في كل مرة يتم فيها بدء النشاط. على الرغم من أن إنشاء الموارد المتكرر يتم تجنبه ، فإن هذه الكتابة ستؤدي إلى تسرب ذاكرة ، لأن الفئة الداخلية غير المستقلة ستعقد مراجعًا إلى الفئات الخارجية بشكل افتراضي ، وستخلق الفئة الداخلية غير الستائية مثيلًا ثابتًا ، ودورة حياة المثيل لا يمكن أن تكون على أساس الاستقالة دائمًا. الطريقة الصحيحة للقيام بذلك هي:
قم بتعيين الفئة الداخلية كطبقة داخلية ثابتة أو استخراج الفئة الداخلية وتغليفها في المفرد. إذا كنت بحاجة إلى استخدام السياق ، فيرجى اتباع السياق الموصى به أعلاه لاستخدام التطبيق. بالطبع ، فإن سياق التطبيق ليس كليًا ، لذلك لا يمكن استخدامه بشكل عشوائي. في بعض الأماكن ، يجب عليك استخدام سياق النشاط. سيناريوهات التطبيق لسياق التطبيق والخدمة والنشاط هي كما يلي:
حيث: NO1 يعني أن التطبيق والخدمة يمكن أن يبدأوا نشاطًا ، ولكن يجب إنشاء قائمة انتظار مهمة جديدة. للحوار ، لا يمكن إنشاؤه إلا في النشاط
فئة داخلية مجهولة
غالبًا ما يرث تطوير Android تنفيذ النشاط/الشظية/العرض. في هذا الوقت ، إذا كنت تستخدم فصولًا مجهولة المصدر وتم الاحتفاظ بها بواسطة مؤشرات ترابط غير متزامنة ، فاحرص على توخي الحذر. إذا لم يكن هناك تدبير ، فإنه بالتأكيد سيؤدي إلى تسرب.
يمتد النشاط الرئيسي للطبقة العامة النشاط {... runnable ref1 = new myrunable () ؛ runnable ref2 = new RunNable () {OverRidepublic void run () {}} ؛ ...}الفرق بين REF1 و REF2 هو أن REF2 يستخدم فئات داخلية مجهولة. لنلقي نظرة على الذاكرة المشار إليها في وقت التشغيل:
كما ترون ، المرجع 1 ليس مميزًا.
ولكن هناك مرجع إضافي في كائن التنفيذ للفئة المجهولة المرجع 2:
هذا المرجع 0 دولار إلى MainActivity. وهذا هو ، سيتم الاحتفاظ بمثال الرئيسي الحالي بواسطة REF2. إذا تم تمرير هذا المرجع إلى مؤشر ترابط غير متزامن ، ويلتأم هذا الموضوع ودورة حياة النشاط ، وسوف يحدث تسرب النشاط.
تسرب الذاكرة الناجم عن المعالج
يجب أن يقال إن مشكلة تسرب الذاكرة الناجمة عن استخدام المعالج هي الأكثر شيوعًا. من أجل تجنب ANR ، لا نقوم بإجراء عمليات تستغرق وقتًا طويلاً على الخيط الرئيسي ، ونستخدم المعالج للتعامل مع مهام الشبكة أو تغليف بعض عمليات استدعاء الطلبات وواجهة برمجة التطبيقات الأخرى. ومع ذلك ، المعالج ليس كليًا. إذا تم كتابة رمز المعالج بطريقة موحدة ، فقد يتسبب ذلك في تسرب الذاكرة. بالإضافة إلى ذلك ، نعلم أن المعالج والرسائل و MissionQueue كلها مرتبطة ببعضها البعض. في حالة عدم معالجة الرسالة التي تم إرسالها بواسطة Handler بعد ، فإن الرسالة وكائن المعالج الذي أرسلها سيتم الاحتفاظ بها بواسطة MessageQueue.
نظرًا لأن المعالج ينتمي إلى متغيرات TLS (الخيط المحلي للتخزين) ، فإن دورة الحياة والنشاط غير متناسقين. لذلك ، يصعب عمومًا أن طريقة التنفيذ هذه تتوافق مع دورة حياة العرض أو النشاط ، لذلك من السهل التسبب في الإصدار الصحيح.
على سبيل المثال:
تمتد عينة من الفئة العامة إلى تمديد النشاط {المعالج النهائي الخاص mleakyHandler = New Handler () {Overridepublic void handlemessage (message msg) {// ...}}@overProtected void onCreate (bundledinstancestate) {super.oncreat MENORE.MLEAKYHandler.PostDelayed (new RunNable () {OverRidepublic void Run () {/ * ... */}} ، 1000 * 60 * 10) ؛ // العودة إلى Activity.Finish () ؛}}يتم الإعلان عن رسالة رسالة تأخرت رسالة لمدة 10 دقائق في العينة ، ويدفعها MleakyHandler إلى رسالة قائمة انتظار الرسائل. عندما يتم إسقاط النشاط بواسطة Finish () ، فإن الرسالة التي تشير إلى تأخير تنفيذ المهمة ستستمر في الوجود في الخيط الرئيسي ، والذي يحمل مرجع المعالج للنشاط ، وبالتالي فإن النشاط الذي انخفض عن طريق الانتهاء () لن يتم إعادة تدويره ، مما يسبب تسرب الذاكرة (لأن المعالج هو فئة داخلية غير منتظمة ، فإنه سيحمل إشارات إلى الفئة الخارجية ، والتي تسد Sampleacity هنا).
إصلاح: تجنب استخدام فئات داخلية غير قاسية في النشاط. على سبيل المثال ، إذا أعلننا أن المعالج ثابت أعلاه ، فإن فترة البقاء على قيد الحياة لا علاقة لها بدورة حياة النشاط. في الوقت نفسه ، يتم إدخال النشاط من خلال المراجع الضعيفة لتجنب تمرير النشاط مباشرة كسياق. انظر الرمز التالي:
إن عينة من الطبقة العامة تعمل على توسيع نطاق النشاط {/*** مثيلات الفئات الداخلية الثابتة لا تحمل إشارة ضمنية*إلى فئة الخارجية. HandleMessage (رسالة msg) {نشاط samplectivity = mactivity.get () {Overridepublic void Run () {/ * ... */}} ؛@overrideprotected void onCreate (bundle saveInstancestate) {super.oncreate (SaveInstancestate) ؛ // push message a execution لمدة 10 دقائق. handler.postdelayed (srunnable ، Activity.finish () ؛}}نظرة عامة ، يوصى باستخدام الفئة الداخلية الثابتة + الضعف. كن حذرًا في أن تكون فارغًا قبل كل استخدام.
تم ذكر DepReference في وقت سابق ، لذلك سأتحدث هنا لفترة وجيزة عن العديد من الأنواع المرجعية من كائنات Java.
لدى Java أربع فئات من المراجع: مرجع قوي ، softreference ، الضعف ، و phatomreference.
في تطوير تطبيقات Android ، من أجل منع تفوق الذاكرة ، عند التعامل مع بعض الكائنات التي تشغل ذاكرة كبيرة ولديها دورة إعلان طويلة ، يمكن استخدام تقنيات مرجعية ناعمة وضعف قدر الإمكان.
يمكن استخدام المراجع الناعمة/الضعيفة بالاقتران مع قائمة انتظار مرجعية (مرجعية). إذا تم إعادة تدوير الكائن المشار إليه بواسطة المرجع الناعم بواسطة جامع القمامة ، فسيضيف جهاز Java Virtual مرجعًا ناعمًا إلى قائمة الانتظار المرجعية المرتبطة به. يتيح لك قائمة الانتظار هذه معرفة القائمة المعاد تدويرها للمراجع الناعمة/الضعيفة ، وبالتالي تطهير المخزن المؤقت الذي فشل في المراجع الناعمة/الضعيفة.
لنفترض أن تطبيقنا سيستخدم عددًا كبيرًا من الصور الافتراضية ، مثل الصورة الرمزية الافتراضية ، وأيقونة اللعبة الافتراضية ، وما إلى ذلك ، والتي سيتم استخدامها في العديد من الأماكن. إذا قرأت الصورة في كل مرة ، فسيكون ذلك أبطأ لأن قراءة الملف تتطلب تشغيل الأجهزة ، مما سيؤدي إلى انخفاض الأداء. لذلك نحن نعتبر ذاكرة التخزين المؤقت للصورة ونقرأها مباشرة من الذاكرة عند الحاجة. ومع ذلك ، نظرًا لأن الصور تأخذ الكثير من مساحة الذاكرة وذاكرة التخزين المؤقت التي تتطلب العديد من الصور الكثير من الذاكرة ، فقد تكون الاستثناءات OutofMemory أكثر عرضة. في هذا الوقت ، يمكننا التفكير في استخدام تقنيات مرجعية ناعمة/ضعيفة لتجنب هذه المشكلة. ما يلي هو النموذج الأولي للذاكرة التخزين المؤقت:
حدد أولاً hashmap وحفظ الكائن المرجعي الناعم.
خريطة خاصة <string ، softreference <bitmap >> ImageCache = new hashmap <string ، softreference <bitmap >> () ؛
دعنا نحدد طريقة لحفظ المرجع الناعم لنقطات النقطة إلى hashmap.
بعد استخدام المراجع الناعمة ، قبل حدوث استثناء OutofMemory ، يمكن تحرير مساحة ذاكرة هذه الموارد المحاكاة المخزنة ، مما يمنع الذاكرة من الوصول إلى الحد الأعلى وتجنب التعطل.
إذا كنت ترغب فقط في تجنب حدوث استثناء OutofMemory ، فيمكنك استخدام المراجع الناعمة. إذا كنت تهتم أكثر بأداء التطبيق الخاص بك وترغب في إعادة تدوير بعض الكائنات التي تشغل المزيد من الذاكرة في أقرب وقت ممكن ، يمكنك استخدام المراجع الضعيفة.
بالإضافة إلى ذلك ، يمكنك تحديد ما إذا كان الكائن يستخدم بشكل متكرر لتحديد ما إذا تم تحديده للمرجع الناعم أو المرجع الضعيف. إذا كان يمكن استخدام الكائن بشكل متكرر ، فحاول استخدام المراجع الناعمة. إذا لم يتم استخدام الكائن أكثر احتمالًا ، فيمكن استخدامه مع مراجع ضعيفة.
حسنًا ، استمر في العودة إلى الموضوع. كما ذكرنا سابقًا ، قم بإنشاء فئة داخلية معالج ثابت واستخدم إشارات ضعيفة للكائنات التي يحتفظ بها المعالج ، بحيث يمكن أيضًا إعادة تدوير الكائنات التي يحتفظ بها المعالج أثناء إعادة التدوير. ومع ذلك ، على الرغم من أن هذا يتجنب تسرب النشاط ، إلا أنه قد لا يزال هناك رسائل معلقة في قائمة انتظار رسائل مؤشر ترابط Looper ، لذلك يجب علينا إزالة الرسائل الموجودة في رسالة قائمة انتظار الرسائل أثناء التدمير أو إيقاف النشاط.
يمكن للطرق التالية إزالة الرسالة:
public final void removeCallbacks (Runnable R) ؛ public public void removeCallbacks (Runnable R ، toke object) ؛ Public Final void removeCallbacksAndMessages (الرمز المميز للكائن) ؛ الإزالة الفريدة النهائية العامة (int ما) ؛ إزالة الفراغ النهائية العامة (int ماذا ، كائن كائن) ؛
حاول تجنب استخدام متغيرات الأعضاء الثابتة
إذا تم الإعلان عن متغير عضو ثابت ، فإننا نعلم جميعًا أن دورة حياته ستكون هي نفس دورة حياة عملية التطبيق بأكملها.
هذا سوف يسبب سلسلة من المشاكل. إذا كانت عملية التطبيق الخاصة بك مصممة لتكون مقيمًا في الذاكرة ، فحين حتى إذا قام التطبيق بتخفيض الخلفية ، فلن يتم إصدار هذا الجزء من الذاكرة. وفقًا لآلية إدارة الذاكرة الحالية لتطبيقات الأجهزة المحمولة ، سيتم إعادة تدوير عمليات الخلفية التي تمثل كمية كبيرة من الذاكرة أولاً. إذا كان هذا التطبيق قد قام بحماية متبادلة للعمليات ، فسوف يتسبب ذلك في إعادة تشغيل التطبيق بشكل متكرر في الخلفية. عندما يقوم الهاتف بتثبيت التطبيق الذي شاركت في التطوير ، يستهلك الهاتف الطاقة وحركة المرور بين عشية وضحاها ، ويجب إلغاء تثبيت التطبيق الخاص بك أو الصمت من قبل المستخدم.
الإصلاح هنا هو:
لا تهيئة الأعضاء الثابتة في بداية الفصل. يمكن النظر في التهيئة كسول.
في التصميم المعماري ، يجب أن نفكر فيما إذا كان من الضروري حقًا القيام بذلك ومحاولة تجنب ذلك. إذا كانت البنية تحتاج إلى تصميم مثل هذا ، فأنت تتحمل مسؤولية إدارة دورة حياة هذا الكائن.
تجنب تجاوز اللمسات الأخيرة ()
1. يتم تنفيذ طريقة الانتهاء في وقت غير مؤكد ولا يمكن الاعتماد عليه لإطلاق موارد نادرة. أسباب الوقت غير المؤكد هي:
الوقت الذي يكون فيه الجهاز الظاهري غير مؤكد
الوقت الذي يتم فيه جدولة خيط الخفي النهائي غير مؤكد
2. سيتم تنفيذ طريقة الانتهاء مرة واحدة فقط. حتى إذا تم إحياء الكائن ، إذا تم تنفيذ طريقة الانتهاء ، فلن يتم تنفيذها مرة أخرى عندما يكون GC مرة أخرى. السبب هو:
الكائن الذي يحتوي على طريقة الانتهاء ينشئ مرجع اللمسات الأخيرة بواسطة الجهاز الظاهري عندما يكون جديدًا ، ويشير إلى الكائن. عند تنفيذ طريقة الانتهاء ، سيتم إصدار مرجع اللمسات الأخيرة المقابلة للكائن. حتى إذا تم إحياء الكائن في هذا الوقت (أي ، الرجوع إلى الكائن مع مرجع قوي) ، وفي المرة الثانية هي GC ، نظرًا لأن مرجع النهائي لم يعد يتوافق معه ، فلن يتم تنفيذ طريقة النهائيات.
3. يحتاج كائن يحتوي على طريقة الانتهاء إلى المرور عبر جولتين على الأقل من GC قبل إصداره.
تسرب الذاكرة الناجم عن مورد غير مُصنّع
对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
على سبيل المثال:
Bitmap 没调用recycle()方法,对于Bitmap 对象在不使用时,我们应该先调用recycle() 释放内存,然后才它设置为null. 因为加载Bitmap 对象的内存空间,一部分是java 的,一部分C 的(因为Bitmap 分配的底层是通过JNI 调用的)。 而这个recyle() 就是针对C 部分的内存释放。
构造Adapter 时,没有使用缓存的convertView ,每次都在创建新的converView。这里推荐使用ViewHolder。
لخص
对Activity 等组件的引用应该控制在Activity 的生命周期之内; 如果不能就考虑使用getApplicationContext 或者getApplication,以避免Activity 被外部长生命周期的对象引用而泄露。
尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量
Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空Handler 里面的消息。比如在Activity onStop 或者onDestroy 的时候,取消掉该Handler 对象的Message和Runnable.
在Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为null,比如使用完Bitmap 后先调用recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
The above is a summary of the causes of memory leaks in Java introduced to you by the editor and how to avoid memory leaks (super detailed version). I hope it will be helpful to everyone. إذا كان لديك أي أسئلة ، فيرجى ترك رسالة لي وسوف يرد المحرر إليك في الوقت المناسب. Thank you very much for your support to Wulin.com website!