كومة وتحسين الذاكرة
لقد اختبرت اليوم وظيفة فرز البيانات التلقائية للمشروع ، وفرز عشرات الآلاف من السجلات والصور في قاعدة البيانات. عندما كانت العملية قريبة من النهاية ، تم الكشف عن java.lang.outofmemoryerror ، وهو خطأ في مساحة كومة Java. في الماضي ، نادراً ما واجهت أخطاء الذاكرة هذه في برامج الكتابة ، لأن Java لديها آلية جامع القمامة ، لذلك لم أولي الكثير من الاهتمام بها. لقد بحثت اليوم عن بعض المعلومات عبر الإنترنت وفرزتها على هذا الأساس.
1. المكدس والمكدس
تم بناء كومة مع جمع القمامة الجديد المسؤول عن إعادة التدوير
1. عندما يبدأ البرنامج في التشغيل ، يحصل JVM على بعض الذاكرة من نظام التشغيل ، وهو جزء منه هو ذاكرة كومة. عادة ما يتم ترتيب ذاكرة الكومة لأعلى في أسفل عنوان التخزين.
2. الكومة هي منطقة بيانات "وقت التشغيل" ، ويخصص الكائن الذي تم إنشاءه في الفئة مساحة من الكومة ؛
3. يتم إنشاء مساحة تخصيص على الكومة من خلال تعليمات مثل "جديد". الكومة هي حجم الذاكرة المخصص ديناميكيًا ، ولا يلزم إخبار المترجم مقدمًا مقدمًا ؛
4. على عكس C ++ ، تدير Java تلقائيًا الكومة والمكدس ، ويمكن لمجمع القمامة تلقائيًا إعادة تدوير ذاكرة الكومة التي لم تعد تستخدم ؛
5. العيب هو أنه نظرًا لأن الذاكرة مخصصة ديناميكيًا في وقت التشغيل ، فإن سرعة الوصول إلى الذاكرة أبطأ.
المكدس - تخزين الأنواع الأساسية وأنواع المرجع ، بسرعة
1. عادة ما يتم استخدام بنية البيانات الأولى ثم خارجها لحفظ المعلمات والمتغيرات المحلية في الطريقة ؛
2. في جافا ، يتم تخزين جميع متغيرات الأنواع الأساسية (قصيرة ، int ، طويلة ، بايت ، تعويم ، مزدوج ، منطقية ، تشار) وأنواع المرجع في المكدس ؛
3. مساحة المعيشة للبيانات في المكدس تكون عمومًا في النطاقات الحالية (المنطقة المحاطة بـ {...} ؛
4. سرعة الوصول للمكدس أسرع من الكومة ، والثانية فقط إلى السجلات الموجودة مباشرة في وحدة المعالجة المركزية ؛
5. يمكن مشاركة البيانات الموجودة في المكدس ، ويمكن أن تشير المراجع المتعددة إلى نفس العنوان ؛
6. العيب هو أن حجم البيانات وعمر المكدس يجب تحديدها وتفتقر إلى المرونة.
2. إعدادات الذاكرة
1. تحقق من حالة ذاكرة الجهاز الظاهري
long maxControl = Runtime.getRuntime (). maxmemory () ؛ // احصل على أقصى قدر من الذاكرة التي يمكن أن تتحكمها الجهاز الظاهري في الوقت الحالي = وقت التشغيل.
بشكل افتراضي ، MaxControl = 66650112b = 63.5625m من الجهاز الظاهري Java ؛
إذا لم تفعل شيئًا ، فإن CurrentUse تم قياسه على الجهاز الخاص بي = 5177344b = 4.9375m ؛
2. أمر لتعيين حجم الذاكرة
-xms <Size> اضبط حجم كومة Java الأولي: اضبط حجم ذاكرة تهيئة JVM ؛ يمكن ضبط هذه القيمة على أنها -xmx لتجنب ذاكرة إعادة توزيع JVM في كل مرة يتم فيها اكتمال مجموعة القمامة.
-xmx <Size> اضبط الحد الأقصى لحجم كومة Java: اضبط الحد الأقصى لحجم ذاكرة الكومة لـ JVM ؛
-xmn <size>: اضبط حجم الجيل الشاب ، وحجم الكومة بأكمله = حجم الجيل الشاب + حجم الجيل القديم + حجم الجيل الأخير.
-XSS <Size> اضبط حجم مكدس ترابط Java: قم بتعيين حجم ذاكرة مكدس مؤشر ترابط JVM ؛
3. عمليات محددة (1) إعدادات ذاكرة JVM:
افتح Myeclipse (Eclipse) Windows-Preferences-Java مثبتة Jres-Edit-Default VM الوسائط
أدخل: -xmx128m -xms64m -xmn32m -xss16m
(2) إعدادات ذاكرة IDE:
قم بتعديل التكوين تحت -vmargs في myeclipse.ini (أو eclipse.ini في دليل الجذر Eclipse):
(3) إعدادات ذاكرة Tomcat
افتح مجلد Bin في الدليل الجذر لـ Tomcat and Edit Catalina.bat
تعديل إلى: تعيين java_opts = -xms256m -xmx512m
3. تحليل خطأ OutofMemoryerror في كومة Java
عند بدء تشغيل JVM ، يتم استخدام ذاكرة الكومة التي يتم تعيينها بواسطة المعلمة -xms. مع استمرار البرنامج وينشئ المزيد من الكائنات ، يبدأ JVM في توسيع ذاكرة الكومة للاحتفاظ بمزيد من الكائنات. يستخدم JVM أيضًا جامع القمامة لإعادة تدوير الذاكرة. عندما يتم الوصول إلى الحد الأقصى لذاكرة الكومة التي تم تعيينها بواسطة -xmx تقريبًا ، إذا لم يتم تخصيص ذاكرة أخرى للكائن الجديد ، فسيقوم JVM برمي java.lang.outofmemoryRor وسيتعطل البرنامج. قبل إلقاء OutofMemoryError ، سيحاول JVM تحرير مساحة كافية مع جامع القمامة ، ولكنه سوف يرمي هذا الخطأ عندما يجد أنه لا يزال هناك مساحة كافية. لحل هذه المشكلة ، يجب أن تكون واضحًا بشأن المعلومات المتعلقة بكائنات البرنامج ، مثل الكائنات التي تم إنشاؤها ، والتي تشغل الكائنات مقدار المساحة ، وما إلى ذلك ، يمكنك استخدام محلل Profiler أو Cheap للتعامل مع أخطاء OutofMemoryerror. "java.lang.outofmemoryerror: Java Heap Space" يعني أن الكومة لا تملك مساحة كافية ولا يمكنها الاستمرار في التوسع. "java.lang.outofmemoryerror: Permgen Space" يعني أن الجيل الدائم ممتلئ ، ولم يعد برنامجك قادرًا على تحميل الفصل أو تخصيص سلسلة.
4. مجموعة الكومة والقمامة
نحن نعلم أن الكائنات يتم إنشاؤها في ذاكرة الكومة ، وجمع القمامة هي عملية تطهير الكائنات الميتة من مساحة الكومة وإرجاع هذه الذاكرة إلى الكومة. من أجل استخدام جامع القمامة ، يتم تقسيم الكومة بشكل أساسي إلى ثلاثة مناطق ، وهي الجيل الجديد ، الجيل القديم أو الجيل المُثبّت ، ومساحة بيرم. الجيل الجديد هو مساحة تستخدم لتخزين الكائنات التي تم إنشاؤها حديثًا ويتم استخدامها عندما يتم إنشاء الكائن حديثًا. إذا تم استخدامها لفترة طويلة ، فسيتم نقلها إلى الجيل القديم (أو الجيل المأثور) بواسطة جامع القمامة. Perm Space هي المكان الذي تقوم فيه JVM بتخزين بيانات التعريف ، مثل الفئات والأساليب وتجمعات السلسلة وتفاصيل مستوى الفصل.
5. ملخص:
1. ذاكرة كومة Java هي جزء من الذاكرة المخصصة لـ JVM بواسطة نظام التشغيل.
2. عندما نقوم بإنشاء كائنات ، يتم تخزينها في ذاكرة كومة Java.
3. لتسهيل جمع القمامة ، يتم تقسيم مساحة كومة Java إلى ثلاثة مناطق ، تسمى الجيل الجديد ، الجيل القديم أو الجيل المُثبّت ، ومساحة بيرم.
4. يمكنك ضبط حجم مساحة كومة Java باستخدام خيارات سطر الأوامر JVM -XMs و -xmx و -xmn.
5. يمكنك استخدام jConsole أو Runtime.maxmemory () ، وقت التشغيل.
6. يمكنك استخدام الأمر "jmap" للحصول على تفريغ الكومة ، واستخدام "JHAT" لتحليل مكب الكومة.
7. مساحة كومة جافا تختلف عن مساحة المكدس. يتم استخدام مساحة المكدس لتخزين مكدسات المكالمات والمتغيرات المحلية.
8. يتم استخدام جامع القمامة Java لاستعادة الذاكرة التي تشغلها كائنات ميتة (الكائنات التي لم تعد تستخدم) وإطلاقها في مساحة كومة Java.
9. عند مواجهة java.lang.outofmemoryerror ، لا داعي للقلق. في بعض الأحيان تحتاج فقط إلى زيادة مساحة الكومة. ولكن إذا حدث ذلك بشكل متكرر ، فيجب عليك معرفة ما إذا كان هناك تسرب ذاكرة في برنامج Java.
10. استخدم أدوات تحليل تفريغ البذيئة والكومية لعرض مساحة كومة Java ، ويمكنك معرفة مقدار الذاكرة المخصصة لكل كائن.
شرح مفصل لتخزين المكدس
تخزين مكدس Java له الخصائص التالية:
1. يجب تحديد حجم البيانات ودورة الحياة في المكدس.
على سبيل المثال ، تخزين النوع الأساسي: int a = 1 ؛ يحتوي هذا المتغير على قيمة حرفية ، A هو إشارة إلى نوع INT ، مشيرًا إلى القيمة الحرفية 3. نظرًا لحجم هذه البيانات الحرفية وعمرها ، يتم تحديد هذه القيم الحرفية بشكل ثابت في كتلة البرنامج ، وبعد خروج كتلة البرنامج ، تختفي القيم الحرفية) ، موجودة في المكدس من أجل متابعة السرعة.
2. يمكن مشاركة البيانات الموجودة في المكدس.
(1) تخزين بيانات النوع الأساسي:
يحب:
int a = 3 ؛ int b = 3 ؛
المعالجة المترجم أولاً int a = 3 ؛ أولاً ، سيؤدي ذلك إلى إنشاء مرجع إلى المتغير A في المكدس ، ثم اكتشف ما إذا كان هناك عنوان ذي قيمة حرفية 3. إذا لم يتم العثور عليه ، فسيفتح عنوانًا ذو قيمة حرفية 3 ، ثم قم بإشارة A إلى عنوان 3. ثم معالجة int B = 3 ؛ بعد إنشاء المتغير المرجعي لـ B ، نظرًا لوجود قيمة حرفية بالفعل 3 في المكدس ، يُشار بشكل مباشر إلى عنوان 3. وبهذه الطريقة ، يشير كلا و B إلى 3 في نفس الوقت.
ملاحظة: يختلف هذه المرجع الحرفي عن كائنات الفئة. على افتراض أن مراجع كائنين من الفئة تشير إلى كائن في نفس الوقت ، إذا قام متغير مرجع كائن واحد بتغيير الحالة الداخلية للكائن ، فإن متغير مرجع الكائن الآخر يعكس هذا التغيير على الفور. بدلاً من ذلك ، لن يتسبب تعديل قيمته من خلال مرجع حرفي في تغيير قيمة أخرى وفقًا لذلك. كما في المثال أعلاه ، بعد تحديد قيم A و B ، دع A = 4 ؛ بعد ذلك ، لن تكون B مساوية لـ 4 ، أو تساوي 3. داخل المترجم ، عند مواجهة A = 4 ، ستعيد البحث عما إذا كانت هناك قيمة حرفية تبلغ 4 في المكدس. إذا لم يكن الأمر كذلك ، أعد فتح العنوان لتخزين قيمة 4 ؛ إذا كان موجودًا بالفعل ، فقم مباشرة بتوضيح A إلى هذا العنوان. لذلك ، لن يؤثر التغيير في القيمة A على القيمة ب.
(2) تخزين بيانات التغليف:
الفئات التي تفتت أنواع البيانات الأساسية المقابلة ، مثل عدد صحيح ، مزدوج ، سلسلة ، إلخ. كل هذه البيانات الفئة موجودة في الكومة. تستخدم Java عبارة جديدة () لعرض المترجم ويقوم فقط بإنشاء ديناميكي فقط حسب الحاجة في وقت التشغيل ، لذلك يكون أكثر مرونة ، ولكن العيب هو أنه يستغرق المزيد من الوقت.
على سبيل المثال: خذ سلسلة كمثال.
السلسلة هي بيانات تغليف خاصة. وهذا هو ، يمكن إنشاؤه في شكل سلسلة str = سلسلة جديدة ("ABC") ؛ أو يمكن إنشاؤها في شكل سلسلة str = "abc" ؛. السابق هو عملية إنشاء الفئة الموحدة ، أي في Java ، كل شيء هو كائن ، وكائن هو مثيل للفئة ، وكلها تم إنشاؤها في شكل جديد (). يمكن لبعض الفصول في Java ، مثل فئة DateFormat ، إرجاع فصل تم إنشاؤه حديثًا من خلال طريقة GetInstance () للفئة ، والتي يبدو أنها تنتهك هذا المبدأ. في الواقع ، هذا ليس هو الحال. يستخدم هذا الفئة نمط Singleton لإرجاع مثيل للفصل ، ولكن يتم إنشاء هذا المثيل داخل الفصل من خلال New () ، الذي يخفي هذا التفاصيل من الخارج.
إذن لماذا لم يتم إنشاء المثيل من خلال New () في String str = "ABC" ؛؟ هل ينتهك المبدأ أعلاه؟ في الواقع ، ليس هناك.
حول العمل الداخلي للسلسلة str = "ABC". تقوم Java داخليًا بتحويل هذا البيان إلى الخطوات التالية:
أ. أولاً ، حدد متغير مرجع كائن اسمه STR إلى فئة السلسلة: String Str ؛
ب. ابحث عن ما إذا كان هناك عنوان ذو قيمة "ABC" في المكدس. إذا لم يكن الأمر كذلك ، افتح عنوانًا مع القيمة الحرفية "ABC" ، ثم قم بإنشاء كائن جديد من فئة السلسلة ، وقم بتوجيه قيمة سلسلة O إلى هذا العنوان ، ولاحظ الكائن المرجعي O بجانب هذا العنوان في المكدس. إذا كان هناك عنوان ذو قيمة "ABC" ، فابحث عن كائن O وأرجع عنوان O.
ج. نقطة str إلى عنوان الكائن O.
تجدر الإشارة إلى أنه عادة ما يتم تخزين قيم السلسلة في فئة السلسلة مباشرة. ولكن في المواقف مثل String str = "ABC" ؛ ، تحتفظ قيمة السلسلة بمرجع إلى البيانات الموجودة في المكدس (أي: String str = "ABC" ؛ كلا تخزين المكدس وتخزين الكومة).
لتوضيح هذه المشكلة بشكل أفضل ، يمكننا التحقق من ذلك من خلال الرموز التالية.
String Str1 = "ABC" ؛ String Str2 = "ABC" ؛ system.out.println (str1 == str2) ؛ //حقيقي
(يتم إرجاع قيمة الحقيقة فقط إذا كانت كلا المرجعين تشير إلى نفس الكائن. هما Str1 و Str2 يشيران إلى نفس الكائن)
توضح النتيجة أن JVM قام بإنشاء مرجعين str1 و str2 ، ولكن تم إنشاء كائن واحد فقط ، وأشار كلا المرجعين إلى هذا الكائن.
String Str1 = "ABC" ؛ String Str2 = "ABC" ؛ str1 = "bcd" ؛ System.out.println (str1 + "،" + str2) ؛ // BCD ، ABC System.out.println (Str1 == Str2) ؛ //خطأ شنيع
هذا يعني أن التغيير في المهمة ينتج عنه التغيير في مرجع كائن الفئة ، يشير STR1 إلى كائن جديد آخر ، بينما لا يزال STR2 يشير إلى الكائن الأصلي. في المثال أعلاه ، عندما نغير قيمة STR1 إلى "BCD" ، وجد JVM أنه لا يوجد عنوان لتخزين القيمة في المكدس ، لذلك فتح هذا العنوان وإنشاء كائن جديد تشير قيمة السلسلة إلى هذا العنوان.
في الواقع ، تم تصميم فئة السلسلة لتكون فئة ثابتة. إذا كنت ترغب في تغيير قيمتها ، فيمكنك ذلك ، لكن JVM ينشئ بهدوء كائن جديد يعتمد على القيمة الجديدة في وقت التشغيل (لا يمكن تغييره بناءً على الذاكرة الأصلية) ، ثم يقوم بإرجاع عنوان هذا الكائن إلى مرجع الفئة الأصلية. على الرغم من أن عملية الإنشاء هذه تلقائية تمامًا ، إلا أنها تستغرق المزيد من الوقت بعد كل شيء. في بيئة أكثر حساسية لمتطلبات الوقت ، سيكون لها بعض الآثار الضارة.
String Str1 = "ABC" ؛ String Str2 = "ABC" ؛ str1 = "bcd" ؛ سلسلة str3 = str1 ؛ System.out.println (Str3) ؛ // bcd String str4 = "bcd" ؛ System.out.println (str1 == Str4) ؛ //حقيقي
يشير الإشارة إلى كائن STR3 مباشرة إلى الكائن الذي يشير إليه STR1 (لاحظ أن STR3 لا ينشئ كائنًا جديدًا). بعد أن قامت STR1 بتغيير قيمتها ، قم بإنشاء مرجع String STR4 وأشار إلى كائن جديد تم إنشاؤه بواسطة STR1 تعديل القيمة. يمكن العثور على أن STR4 هذه المرة لم ينشئ كائنًا جديدًا ، وبالتالي إدراك مشاركة البيانات في المكدس مرة أخرى.
String str1 = سلسلة جديدة ("ABC") ؛ String Str2 = "ABC" ؛ system.out.println (str1 == str2) ؛ //خطأ شنيعتم إنشاء مرجعين. تم إنشاء كائنين. تشير المرجعان إلى كائنين مختلفين.
String Str1 = "ABC" ؛ String Str2 = سلسلة جديدة ("ABC") ؛ system.out.println (str1 == str2) ؛ //خطأ شنيعتم إنشاء مرجعين. تم إنشاء كائنين. تشير المرجعان إلى كائنين مختلفين.
يشير الرموز المذكورة أعلاه إلى أنه طالما تم إنشاء الكائن باستخدام New () ، سيتم إنشاؤه في الكومة ، ويتم تخزين سلاسله بشكل منفصل. حتى لو كانت مثل البيانات الموجودة في المكدس ، فلن تتم مشاركتها مع البيانات الموجودة في المكدس.
تلخيص:
(1) عندما نحدد فئة باستخدام تنسيق مثل String str = "ABC" ؛ ، نأخذها دائمًا كأمر مسلم به أننا أنشأنا كائنًا من فئة السلسلة. تقلق بشأن الفخ! قد لا يكون الكائن قد تم إنشاؤه! الشيء الوحيد المؤكد هو إنشاء إشارة إلى فئة السلسلة. بالنسبة إلى ما إذا كان هذا المرجع يشير إلى كائن جديد ، يجب النظر فيه بناءً على السياق ، ما لم تستخدم الطريقة الجديدة () لإنشاء كائن جديد بشكل صريح. لذلك ، لكي نكون أكثر دقة ، نقوم بإنشاء متغير مرجعي إلى كائن من فئة السلسلة ، والذي يشير إلى فئة سلسلة ذات قيمة "ABC". إن إدراك ذلك مفيد في استكشاف الأخطاء الصعبة في البرامج.
(2) باستخدام String str = "ABC" ؛ يمكن تحسين سرعة تشغيل البرنامج إلى حد ما ، لأن JVM ستقرر تلقائيًا ما إذا كان من الضروري إنشاء كائن جديد بناءً على الموقف الفعلي للبيانات في المكدس. بالنسبة إلى رمز السلسلة STR = سلسلة جديدة ("ABC") ؛ ، يتم إنشاء كائنات جديدة في الكومة بغض النظر عما إذا كانت قيم السلسلة متساوية أم لا ، ما إذا كان من الضروري إنشاء كائنات جديدة ، وبالتالي زيادة العبء على البرنامج.
(3) نظرًا للطبيعة غير القابلة للتغيير لفئة السلسلة (لأنه لا يمكن تعديل قيمة فئة Wrapper) ، عندما يحتاج متغير السلسلة بشكل متكرر ، يجب النظر في فئة StringBuffer لتحسين كفاءة البرنامج.