يقدم هذا المقال مبادئ إدارة الذاكرة Java وأسباب تسرب الذاكرة بالتفصيل.
آلية إدارة الذاكرة جافا
في C ++ ، إذا كانت هناك حاجة إلى قطعة من الذاكرة لتخصيص قطعة من الذاكرة ديناميكيًا ، فيجب أن يكون المبرمج مسؤولاً عن دورة حياة هذه القطعة بأكملها. من طلب التخصيص ، إلى الاستخدام ، إلى الإصدار النهائي. هذه العملية مرنة للغاية ، لكنها مرهقة للغاية. قامت لغة Java بعمل تحسينها الخاص لإدارة الذاكرة ، وهي آلية جمع القمامة. يتم تخصيص جميع كائنات الذاكرة تقريبًا في Java على ذاكرة الكومة (باستثناء أنواع البيانات الأساسية) ، ثم تكون GC (GAGE Collection) مسؤولة عن إعادة التدوير تلقائيًا والتي لم تعد تستخدم.
ما سبق هو الوضع الأساسي لآلية إدارة الذاكرة Java. ولكن إذا فهمنا هذا فقط ، فسوف نواجه تسرب الذاكرة في تطوير المشروع الفعلي. قد يشك بعض الناس في أنه نظرًا لأن آلية جمع القمامة في Java يمكنها تلقائيًا إعادة تدوير الذاكرة ، فلماذا لا تزال هناك تسرب ذاكرة؟ في هذا السؤال ، نحتاج إلى معرفة متى تقوم GC بإعادة تدوير كائنات الذاكرة ونوع كائنات الذاكرة التي سيتم اعتبارها "لم تعد تستخدم" بواسطة GC.
يستخدم الوصول إلى كائنات الذاكرة في Java الطرق المرجعية. في رمز Java ، نحافظ على متغير مرجعي لكائن الذاكرة. في برامج Java ، يمكن تخزين هذا المتغير المرجعي نفسه في ذاكرة الكومة وفي ذكرى مكدس الرمز (مثل نوع البيانات الأساسية). تبدأ مؤشرات ترابط GC في التتبع من المتغيرات المرجعية في مكدس الرمز لتحديد الذاكرة المستخدمة. إذا لم يتمكن مؤشر ترابط GC من تتبع قطعة من ذاكرة الكومة بهذه الطريقة ، فإن GC تعتقد أنه لن يتم استخدام قطعة الذاكرة هذه (لأن الكود لم يعد قادرًا على الوصول إلى هذه الذاكرة).
من خلال طريقة إدارة ذاكرة الرسم البياني الموجه ، عندما يفقد كائن الذاكرة جميع المراجع ، يمكن لـ GC إعادة تدويرها. على العكس من ذلك ، إذا كان الكائن لا يزال لديه مرجع ، فلن يتم إعادة تدويره بواسطة GC ، حتى لو كان الجهاز الظاهري Java يرمي OutofMemoryerror.
تسرب الذاكرة جافا
بشكل عام ، هناك حالتان لتسرب الذاكرة. في إحدى الحالات ، يتم حذف جميع الذاكرة المخصصة في الكومة (مثل إعادة تعيين المؤشر) عندما لا يتم إصداره ؛ الذاكرة وطريقة الوصول الخاصة بها (مرجع). الحالة الأولى هي أنه تم حلها جيدًا في Java بسبب إدخال آلية جمع القمامة. لذلك ، تشير تسرب الذاكرة في Java بشكل رئيسي إلى الحالة الثانية.
ربما مجرد الحديث عن هذا المفهوم مجردة للغاية ، يمكنك إلقاء نظرة على مثل هذا المثال:
نسخة الكود كما يلي:
ناقل V = ناقل جديد (10) ؛
لـ (int i = 1 ؛ i <100 ؛ i ++) {
كائن o = كائن جديد () ؛
v.add (o) ؛
o = فارغة ؛
}
في هذا المثال ، هناك إشارات إلى كائن المتجه V والمراجع إلى كائن كائن O في مكدس الرمز. في الحلقة ، نقوم باستمرار بإنشاء كائنات جديدة ، ثم نضفها إلى كائن المتجه ، ثم تفريغ مرجع O. والسؤال هو ، في حالة حدوث GC بعد مرجع o فارغ ، هل سيتم إعادة تدوير كائن الكائن الذي ننشئه بواسطة GC؟ الجواب لا. لأنه عندما يتتبع GC المراجع في مكدس الكود ، ستجد مرجع V ، ويستمر في تعقب ، سيجد أن هناك مرجعًا إلى كائن الكائن في مساحة الذاكرة التي يشير إليها مرجع V. وهذا يعني ، على الرغم من أن مرجع O كان فارغًا ، لا تزال هناك مراجع أخرى في كائن الكائن ويمكن الوصول إليها ، لذلك لا يمكن لـ GC إطلاقها. إذا لم يكن للكائن أي تأثير على البرنامج بعد هذه الحلقة ، فإننا نعتقد أن تسرب الذاكرة حدث في برنامج Java هذا.
على الرغم من أن تسريبات ذاكرة Java أقل تدميراً لتسريبات الذاكرة في C/C ++ ، إلا أنه لا يزال بإمكان البرنامج تشغيل بشكل طبيعي في معظم الحالات باستثناء الحالات القليلة التي يتعطل فيها البرنامج. ومع ذلك ، عندما يكون للأجهزة المحمولة قيود صارمة على الذاكرة ووحدة المعالجة المركزية ، فإن تجاوز ذاكرة Java سيؤدي إلى عدم كفاءة البرامج وشغل كميات كبيرة من الذاكرة غير المرغوب فيها. سيؤدي ذلك إلى تدهور أداء الجهاز بأكمله ، وفي الحالات الشديدة ، سيؤدي ذلك أيضًا إلى إلقاء OutofMemoryerror ، مما تسبب في تعطل البرنامج.
تجنب تسرب الذاكرة بشكل عام
بشكل عام ، دون إشراك هياكل بيانات معقدة ، تتجلى تسرب ذاكرة Java حيث تتجاوز دورة حياة كائن الذاكرة طول الوقت الذي يحتاجه البرنامج. في بعض الأحيان نسميها "كائن مجاني".
على سبيل المثال:
نسخة الكود كما يلي:
الفئة العامة FileSearch {
محتوى بايت خاص [] ؛
ملف خاص mfile ؛
Public Filesearch (ملف الملف) {
mfile = ملف ؛
}
منطقية عامة Hasstring (String str) {
int size = getFilesize (mfile) ؛
المحتوى = بايت جديد [الحجم] ؛
loadfile (mfile ، content) ؛
سلسلة s = سلسلة جديدة (المحتوى) ؛
إرجاع s.contains (str) ؛
}
}
في هذا الرمز ، هناك دالة hasstring في فئة Filesearch لتحديد ما إذا كان المستند يحتوي على السلسلة المحددة. تتمثل العملية في تحميل Mfile في الذاكرة أولاً ثم إصدار أحكام. ومع ذلك ، فإن المشكلة هنا هي أن المحتوى يتم الإعلان عنه كمتغير مثيل ، وليس متغيرًا محليًا. لذلك ، بعد إرجاع هذه الوظيفة ، لا تزال بيانات الملف بأكمله موجودة في الذاكرة. من الواضح أننا لم نعد بحاجة إلى هذه البيانات في المستقبل ، مما يؤدي إلى مضيعة غير معقولة للذاكرة.
لتجنب تسرب الذاكرة في هذه الحالة ، يتعين علينا إدارة ذاكرتنا المخصصة مع التفكير في إدارة الذاكرة C/C ++. أولاً ، هو توضيح النطاق الفعال لكائن الذاكرة قبل إعلان مرجع الكائن. يجب الإعلان عن كائنات الذاكرة صالحة داخل الوظيفة كمتغيرات محلية ، وتلك التي لديها نفس دورة الحياة التي يجب إعلان مثيل الفئة كمتغيرات مثيل ... وما إلى ذلك. ثانياً ، تذكر أن تفريغ كائن الذاكرة يدويًا عندما لم تعد هناك حاجة إليها.
مشكلة تسرب الذاكرة في هياكل البيانات المعقدة
في المشاريع الفعلية ، غالبًا ما نستخدم بعض هياكل البيانات الأكثر تعقيدًا لتخزين معلومات البيانات اللازمة أثناء تشغيل البرنامج. في بعض الأحيان ، نظرًا لتعقيد بنية البيانات ، أو لدينا بعض الاحتياجات الخاصة (على سبيل المثال ، أكبر قدر ممكن من معلومات ذاكرة التخزين المؤقت لتحسين سرعة تشغيل البرنامج ، وما إلى ذلك) ، من الصعب علينا التعامل مع البيانات في بنية البيانات. في هذا الوقت ، يمكننا استخدام آلية خاصة في Java لمنع تسرب الذاكرة.
لقد قدمنا قبل أن آلية GC الخاصة بـ Java تستند إلى آلية المرجع التي تتبع الذاكرة. قبل ذلك ، حددنا المراجع فقط شكل من أشكال "كائن O ؛". في الواقع ، هذا مجرد موقف افتراضي في آلية مرجع Java ، وهناك بعض الطرق المرجعية الأخرى بالإضافة إلى ذلك. باستخدام هذه الآليات المرجعية الخاصة والدمج مع آلية GC ، يمكننا تحقيق بعض الآثار التي نحتاجها.
عدة طرق مرجعية في جافا
هناك العديد من الطرق المختلفة للاستشهاد في جافا ، وهي: اقتباس قوي ، استشهاد ناعم ، اقتباس ضعيف ، اقتباس افتراضي. بعد ذلك ، نفهم أولاً أهمية طرق الاقتباس هذه بالتفصيل.
اقتباس قوي
كانت الاستشهادات المستخدمة في المحتوى الذي أدخلناه من قبل جميع الاستشهادات القوية ، والتي هي أكثر الاستشهادات شيوعًا المستخدمة. إذا كان للكائن مرجعًا قويًا ، فهو مشابه لضرورة يومية أساسية ، ولن يقوم جامع القمامة بإعادة تدويره أبدًا. عندما تكون مساحة الذاكرة غير كافية ، تفضل جهاز Java Virtual إلقاء خطأ OutofMemoryError للتسبب في انتهاء البرنامج بشكل غير طبيعي من إعادة تدوير الكائنات مع إشارات قوية لحل مشكلة الذاكرة.
softreference
الاستخدام النموذجي لفئة Softreference مخصصة لذاكرة التخزين المؤقت الحساسة للذاكرة. يتمثل مبدأ الإحسان في التأكد من مسح جميع المراجع الناعمة قبل أن تقارير JVM ذاكرة غير كافية عند الاحتفاظ بالإشارة إلى كائن. النقطة الأساسية هي أن جامع القمامة قد (أو لا) قد يطلق كائنات ناعمة يمكن الوصول إليها في وقت التشغيل. ما إذا كان يتم تحرير كائن ما على خوارزمية جامع القمامة وكمية الذاكرة المتاحة عند تشغيل جامع القمامة.
ضعف
الاستخدام النموذجي لفئة الضعف هو تطبيع التعيين (رسم الخرائط الكنسي). بالإضافة إلى ذلك ، تعد المراجع الضعيفة مفيدة أيضًا للكائنات ذات العمر الطويل نسبيًا وانخفاض النفقات العامة الترفيهية. النقطة الأساسية هي أنه إذا تم مواجهة كائن يمكن الوصول إليه بشكل ضعيف أثناء تشغيل جامع القمامة ، فسيتم إصدار الكائن المشار إليه بواسطة PreferFence. ومع ذلك ، لاحظ أن جامع القمامة قد يضطر إلى تشغيل عدة مرات قبل أن يتمكن من العثور على كائنات يمكن الوصول إليها بشكل ضعيف وإطلاقها.
الوهمية
لا يمكن استخدام فئة PhantomReference إلا لتتبع مجموعات القادمة من الكائنات المرجعية. وبالمثل ، يمكن استخدامه أيضًا لإجراء عمليات المقاصة قبل الوفاة. يجب استخدام فانتومري مع فئة مرجعية. مطلوب ReferenceQueue لأنه يمكن أن يكون بمثابة آلية للإخطار. عندما يحدد جامع القمامة أن الكائن هو كائن وصول افتراضي ، يتم وضع كائن فانتومري على مرجعه. يعد وضع كائن phantomreference على المرجعية إشعارًا يشير إلى أن الكائن المشار إليه بواسطة كائن فانتومري قد انتهى ويتوفر للجمع. يتيح لك ذلك اتخاذ إجراءات قبل إعادة تدوير الذاكرة التي يشغلها الكائن. يتم استخدام المرجع والمرجع بالاقتران مع المرجعية.
GC ، المرجع والمرجع
A. لا يمكن لـ GC حذف ذاكرة الكائنات ذات المراجع القوية.
B. GC وجدت ذاكرة كائن مع مراجع ناعمة فقط ، ثم:
① يتم تعيين الحقل المرجعي لكائن Softreference على NULL ، بحيث لا يشير الكائن إلى كائن الكومة.
② يتم الإعلان عن كائن الكومة المشار إليه بواسطة Softreference على أنه نهائي.
③ عند تشغيل طريقة اللمسات الأخيرة على كائن الكومة ويتم إصدار الذاكرة التي يشغلها الكائن ، تتم إضافة كائن Softreference إلى مرجعه (إذا كان هذا الأخير موجودًا).
C. GC يكتشف ذاكرة كائن مع مراجع ضعيفة فقط ، ثم:
① يتم تعيين الحقل المرجعي لكائن DepReference على NULL ، بحيث لا يشير الكائن إلى كائن الكومة.
② يتم الإعلان عن كائن الكومة المشار إليه من خلال الضعف على أنه قابلة للتصفية.
③ عند تشغيل طريقة اللمسات الأخيرة () لكائن الكومة ويتم إصدار الذاكرة التي يشغلها الكائن ، تتم إضافة كائن DepReference إلى مرجعه (إذا كان هذا الأخير موجودًا).
D. GC يكتشف ذاكرة كائن لا تحتوي إلا على مراجع افتراضية ، ثم:
① يتم الإعلان عن كائن الكومة المشار إليه من قبل الوهمية على أنه قابلة للتصفية.
② تتم إضافة فانتومري إلى مرجعه قبل إصدار كائن الكومة.
النقاط التالية تستحق الإشارة:
1. لن تجد GC كائنات ذاكرة مرجعية بشكل عام فقط.
2. اكتشاف GC وإطلاقه للمراجع الضعيفة ليس على الفور.
3. عند إضافة المراجع اللين والمراجع الضعيفة إلى المرجعية ، تم ضبط المراجع إلى الذاكرة الحقيقية على فارغة ، وتم إصدار الذاكرة ذات الصلة. عند إضافة مرجع افتراضي إلى مرجعية ، لم يتم إصدار الذاكرة بعد ولا يزال من الممكن الوصول إليها.
من خلال المقدمة أعلاه ، أعتقد أن لديك فهمًا معينًا لآلية اقتباس Java وأوجه التشابه والاختلاف في عدة طرق للاستشهاد. مجرد مفهوم قد يكون مجردة للغاية.
نسخة الكود كما يلي:
String str = سلسلة جديدة ("Hello") ؛
ReferenceQueue <string> rq = new ReferenceQueue <String> () ؛
PreferReference <string> wf = New Preferference <string> (Str ، Rq) ؛
str = null ؛
String str1 = wf.get () ؛
// إذا لم تتم إعادة تدوير كائن "Hello" ، فإن rq.poll () يعيد خالية
المرجع <؟
في الكود أعلاه ، انتبه إلى المكانين ⑤⑥. إذا لم يتم إعادة تدوير كائن "Hello" WF.GET () سيعود كائن "Hello" NULL ، RQ.POLL () إرجاع كائن مرجعي ، ولكن لا يوجد أي إشارة إلى كائن STR في هذا الكائن المرجعي (تختلف فانتومري من الضعف و softreference).
التطبيق المشترك لآلية الاقتباس وهياكل البيانات المعقدة
من خلال فهم آلية GC ، والآلية المرجعية ، والجمع بين المرجعية ، يمكننا تنفيذ بعض أنواع البيانات المعقدة التي تمنع تدفق الذاكرة.
على سبيل المثال ، يحتوي Softreference على خصائص بناء نظام ذاكرة التخزين المؤقت ، حتى نتمكن من تنفيذ نظام ذاكرة التخزين المؤقت البسيط مع جداول التجزئة. هذا لا يضمن فقط أنه يمكن تخزينه مؤقتًا ، بل يضمن أيضًا أن الجهاز الظاهري Java لن يرمي OutofMemoryerror بسبب تسرب الذاكرة. آلية التخزين المؤقت هذه مناسبة بشكل خاص للحالات التي يكون فيها كائنات الذاكرة دورة حياة طويلة والوقت لإنشاء كائنات الذاكرة طويلة نسبيًا ، مثل صور غطاء قائمة ذاكرة التخزين المؤقت ، إلخ. بالنسبة لبعض الحالات التي تكون فيها دورة الحياة طويلة ولكن النفقات العامة لتوليد كائنات الذاكرة ليست كبيرة ، يمكن أن يحقق استخدام الضعف إدارة ذاكرة أفضل.
يتم إرفاق نسخة من رمز المصدر الخاص بـ SofthashMap.
نسخة الكود كما يلي:
حزمة كوم.
//: softhashmap.java
استيراد java.util.
استيراد java.lang.ref.
استيراد Android.Util.log ؛
يمتد SofthashMap من الفئة العامة الملخص {
!
تجزئة الخريطة النهائية الخاصة = new hashmap () ؛
/** عدد الإشارات "الصعبة" إلى عقد داخلي.
نهائي خاص int hard_size ؛
/** قائمة FIFO من المراجع الصعبة ، وترتيب الوصول الأخير.
Final LinkedList Hardcache = New LinkedList () ؛
/** قائمة الانتظار المرجعية لكائنات softreference المسحية.
قائمة انتظار مرجعية خاصة = مرجعية جديدة () ؛
// رقم مرجعي قوي
softhashmap () {هذا (100) ؛
softhashmap العامة (int hardsize) {hard_size = hardsize ؛
الكائن العام الحصول على (مفتاح الكائن) {
نتيجة الكائن = فارغة ؛
// نحصل على التداخل الذي يمثله هذا المفتاح
softreference soft_ref = (softreference) hash.get (مفتاح) ؛
if (soft_ref! = null) {
// من المؤتمر اللطيف نحصل على القيمة ، والتي يمكن أن تكون
// NULL إذا لم يكن في الخريطة ، أو تمت إزالته
// طريقة ProcessQueue () محددة أدناه
النتيجة = soft_ref.get () ؛
if (النتيجة == null) {
// إذا تم جمع القيمة التي تم جمعها ، فقم بإزالة
// دخول من hashmap.
hash.remove (مفتاح) ؛
} آخر {
// نضيف الآن هذا الكائن إلى بداية الصعبة
// قائمة انتظار مرجعية.
// مرة واحدة ، لأن عمليات البحث في قائمة انتظار FIFO بطيئة ، لذلك
// لا نريد البحث من خلاله في كل مرة لإزالتها
// التكرارات.
// حافظ على كائن الاستخدام الأخير في الذاكرة
hardcache.addfirst (نتيجة) ؛
if (hardcache.size ()> hard_size) {
// قم بإزالة الإدخال الأخير إذا أدرج أطول من Hard_size
hardcache.removelast () ؛
}
}
}
نتيجة العودة
}
/** نحدد الفئة الفرعية الخاصة بنا من الفئة الفرعية التي تحتوي على
ليس فقط القيمة ولكن أيضًا المفتاح لتسهيل العثور عليه
الدخول في Hashmap بعد جمع القمامة.
يمتد SoftValue الثابت الخاص يمتد Softreference {
مفتاح الكائن النهائي الخاص ؛
/** هل تعلم أن الفئة الخارجية يمكنها الوصول إلى البيانات الخاصة
أعضاء وطرق الطبقة الداخلية؟
اعتقدت أن الفصل الداخلي هو الذي يمكنه الوصول إلى
المعلومات الخاصة للطبقة الخارجية يمكن أيضًا
الوصول إلى الأعضاء الخاصين في فصل داخلي داخلها الداخلي
فصل. */
SoftValue الخاص (الكائن K ، مفتاح الكائن ، مرجع Q) {
سوبر (ك ، س) ؛
هذا .key = المفتاح ؛
}
}
/** هنا نذهب من خلال المرجع وإزالة القمامة
تم جمع الأشياء softvalue من hashmap من خلال البحث عنها
UP باستخدام عضو البيانات softvalue.key.
Public Void ProcessQueue () {
SoftValue SV ؛
بينما ((sv = (softvalue) queue.poll ())! = null) {
if (sv.get () == null) {
log.e ("processqueue" ، "null") ؛
} آخر {
log.e ("processqueue" ، "not null") ؛
}
hash.remove (sv.key) ؛
log.e ("softhashmap" ، "الإصدار" + sv.key) ؛
}
}
/** هنا وضعنا المفتاح ، وقيمة الزوج في hashmap باستخدام
كائن SoftValue.
وضع الكائن العام (مفتاح الكائن ، قيمة الكائن) {
Processqueue () ؛
log.e ("softhashmap" ، "وضعت في" + مفتاح) ؛
إرجاع hash.put (المفتاح ، softvalue الجديد (القيمة ، المفتاح ، قائمة الانتظار)) ؛
}
الكائن العام إزالة (مفتاح الكائن) {
Processqueue () ؛
إرجاع hash.remove (مفتاح) ؛
}
الفراغ العام clear () {
hardcache.clear () ؛
Processqueue () ؛
hash.clear () ؛
}
حجم int العام () {
Processqueue () ؛
عودة التجزئة.
}
مجموعة إدخال المجموعة العامة () {
// لا ، لا ، قد لا تفعل ذلك !!!
رمي جديد غير مدعوم جديد () ؛
}
}