1. مقدمة
Java هي لغة برمجة عالية المستوى موجهة إلى كائنات منصة. يتم تشغيل برامج Java على Java Virtual Machines (JVMS) وإدارة الذاكرة بواسطة JVMs. هذا هو الفرق الأكبر من C ++. على الرغم من أن الذاكرة تتم إدارتها بواسطة JVMS ، يجب علينا أيضًا أن نفهم كيف تدير JVM الذاكرة. لا يوجد JVM واحد فقط ، وقد يكون هناك العشرات من الأجهزة الافتراضية موجودة حاليًا ، ولكن تصميم الجهاز الظاهري الذي يتوافق مع المواصفات يجب أن يتبع "مواصفات الجهاز الظاهري Java". تعتمد هذه المقالة على وصف الجهاز الظاهري للنقطة الساخنة ، وسيتم ذكره إذا كانت هناك اختلافات مع الأجهزة الافتراضية الأخرى. تصف هذه المقالة بشكل أساسي كيف يتم توزيع الذاكرة في JVM ، وكيفية تخزين كائنات برنامج Java والوصول إليها ، والاستثناءات المحتملة في مختلف مناطق الذاكرة.
2. توزيع الذاكرة (المنطقة) في JVM
عند تنفيذ برامج JAVA ، يقسم JVM الذاكرة إلى مجالات بيانات مختلفة متعددة للإدارة. هذه المناطق لها وظائف مختلفة وأوقات الخلق والدمار. يتم تخصيص بعض المناطق عند بدء عملية JVM ، بينما يرتبط البعض الآخر بدورة حياة مؤشر ترابط المستخدم (مؤشر ترابط البرنامج نفسه). وفقًا لمواصفات JVM ، تنقسم مناطق الذاكرة التي تديرها JVM إلى مناطق بيانات وقت التشغيل التالية:
1. مكدس الجهاز الظاهري
منطقة الذاكرة هذه خاصة بواسطة الخيط ويتم إنشاؤها عند بدء تشغيل الخيط وتدميره عند تدميره. نموذج الذاكرة لتنفيذ أساليب Java الموصوفة بواسطة مكدس الجهاز الظاهري: ستنشئ كل طريقة إطار مكدس (إطار مكدس) في بداية التنفيذ ، والذي يتم استخدامه لتخزين الجداول المتغيرة المحلية ، ومكدس المعامل ، والروابط الديناميكية ، وخارج الطريقة ، إلخ.
كما يوحي الاسم ، فإن الجدول المتغير المحلي هو منطقة ذاكرة تخزن المتغيرات المحلية: فهو يخزن أنواع البيانات الأساسية (8 أنواع البيانات الأساسية Java) ، وأنواع المراجع ، وعناوين الإرجاع التي يمكن العثور عليها خلال فترة التحويل البرمجي ؛ سوف تشغل الأنواع الطويلة والمزدوجة التي تشغل 64 بت مساحة متغيرة محلية ، وأنواع البيانات الأخرى التي تشغل فقط 1 ؛ نظرًا لأن حجم النوع يتم تحديده ويمكن أن يكون عدد المتغيرات معروفًا خلال فترة الترجمة ، فإن الجدول المتغير المحلي له حجم معروف عند إنشاؤه. يمكن تخصيص هذا الجزء من مساحة الذاكرة خلال فترة التجميع ، وليس هناك حاجة لتعديل حجم الجدول المتغير المحلي أثناء تشغيل الطريقة.
في مواصفات الجهاز الظاهري ، يتم تحديد استثناءان لمنطقة الذاكرة هذه:
1. إذا كان عمق المكدس الذي يطلبه الخيط أكبر من العمق المسموح به (؟) ، فسيتم طرح استثناء StackOverflowError ؛
2. إذا كان الجهاز الظاهري يمكن أن يتوسع ديناميكيًا ، عندما لا يمكن التقدم بالتوسع للحصول على ذاكرة كافية ، فسيتم طرح استثناء OutOfMemory ؛
2. كومة الطريقة المحلية
يعد مكدس الطريقة المحلية أيضًا مؤشرات الترابط ، وتكون وظيفتها تقريبًا مثل مكدس الجهاز الظاهري: توفر مكدس الجهاز الظاهري خدمات مكدس داخل وخارج لتنفيذ طريقة Java ، بينما توفر مكدس الطريقة المحلية للآلة الافتراضية لتنفيذ الأساليب الأصلية.
في مواصفات الجهاز الظاهري ، لا يوجد تنظيم إلزامي على طريقة تنفيذ مكدس الطريقة المحلية ، ويمكن تنفيذه بحرية بواسطة الجهاز الظاهري المحدد ؛ يجمع الجهاز الظاهري للنقطة الساخنة مباشرة بين مكدس الجهاز الظاهري ومكدس الطريقة المحلية في واحدة ؛ بالنسبة للأجهزة الافتراضية الأخرى لتنفيذ هذه الطريقة ، يمكن للقراء الاستعلام عن المعلومات ذات الصلة إذا كانوا مهتمين ؛
مثل مكدس الجهاز الظاهري ، ستقوم مكدس الطريقة المحلية أيضًا بإلقاء استثناءات StackOverflowError和OutOfMemory .
3. حاسبة البرنامج
حاسبة البرنامج هي أيضًا منطقة ذاكرة خاصة من مؤشرات الترابط. يمكن اعتباره مؤشرًا رقم سطر (يشير إلى تعليمات) لتنفيذ ترابط الباقين. عند تنفيذ Java ، فإنه يحصل على التعليمات التالية التي سيتم تنفيذها عن طريق تغيير قيمة العداد. أوامر التنفيذ للفروع ، الحلقات ، القفزات ، معالجة الاستثناء ، استرداد مؤشرات الترابط ، إلخ. تعتمد جميعها على هذا العداد لإكمالها. يتم تحقيق القراءة المتعددة للجهاز الظاهري عن طريق التبديل بدوره وتخصيص وقت تنفيذ المعالج. يمكن للمعالج (جوهر لمعالج متعدد النواة) تنفيذ أمر واحد فقط في وقت واحد. لذلك ، بعد تنفيذ مؤشر الترابط التبديل ، يجب استعادته إلى موضع التنفيذ الصحيح. كل مؤشر ترابط لديه آلة حاسبة برنامج مستقلة.
عند تنفيذ طريقة Java ، تسجل برنامج حاسبة البرنامج (يشير إلى) عنوان تعليمات Bytecode التي ينفذها مؤشر الترابط الحالي. إذا تم تنفيذ الطريقة الأصلية ، فإن قيمة هذه الآلة الحاسبة غير محددة. وذلك لأن نموذج مؤشر ترابط الجهاز الافتراضي Hotspot هو نموذج مؤشر ترابط أصلي ، أي أن كل مؤشر ترابط Java يرسم مباشرة مؤشر ترابط نظام التشغيل (نظام التشغيل). عند تنفيذ الطريقة الأصلية ، يتم تنفيذها مباشرة بواسطة نظام التشغيل. قيمة هذا العداد للجهاز الظاهري عديمة الفائدة ؛ نظرًا لأن هذه الآلة الحاسبة هي منطقة ذاكرة لها مساحة صغيرة جدًا ، خاصة ، ولا تتطلب التوسع. هذا هو المجال الوحيد في مواصفات الجهاز الظاهري الذي لا يحدد أي استثناء OutOfMemoryError .
4. ذاكرة الكومة (كومة)
كومة Java هي منطقة ذاكرة مشتركة بواسطة مؤشرات الترابط. يمكن القول أنها أكبر منطقة ذاكرة تديرها الجهاز الظاهري ويتم إنشاؤها عند بدء تشغيل الجهاز الظاهري. تخزن ذاكرة Java Chep بشكل رئيسي مثيلات الكائنات ، ويتم تخزين جميع مثيلات الكائنات تقريبًا (بما في ذلك المصفوفات) هنا. لذلك ، هذا هو أيضا منطقة الذاكرة الرئيسية لجمع القمامة (GC). لن يتم وصف محتوى GC هنا ؛
وفقًا لمواصفات الجهاز الظاهري ، يمكن أن تكون ذاكرة كومة Java في ذاكرة فعلية متقطعة. طالما أنها مستمرة منطقيا ولا يوجد حد لتوسع المساحة ، يمكن أن يكون إما حجم ثابت أو شجرة ممتدة. إذا لم يكن لدى ذاكرة الكومة مساحة كافية لإكمال تخصيص المثيل ولا يمكن توسيعها ، فسيتم طرح استثناء OutOfMemoryError .
5. منطقة الطريقة
منطقة الطريقة هي منطقة الذاكرة التي تشاركها مؤشرات الترابط ، تمامًا مثل ذاكرة الكومة ، وهي تخزن معلومات النوع والثوابت والمتغيرات الثابتة والرمز الذي تم تجميعه خلال فترة التجميع الفورية وغيرها من البيانات. لا تحتوي مواصفات الجهاز الظاهري على الكثير من القيود المفروضة على تنفيذ منطقة الطريقة ، ومثل ذاكرة الكومة ، فهي لا تتطلب مساحة ذاكرة فعلية مستمرة ، ويمكن إصلاح الحجم أو القابل للتطوير ، ويمكن أيضًا اختياره لعدم تطبيق جمع القمامة ؛ عندما لا تستطيع منطقة الطريقة تلبية متطلبات تخصيص الذاكرة ، سيتم طرح استثناء OutOfMemoryError .
6. الذاكرة المباشرة
الذاكرة المباشرة ليست جزءًا من الذاكرة المدارة للجهاز الظاهري ، ولكن قد لا يزال هذا الجزء من الذاكرة يستخدم بشكل متكرر ؛ عندما تستخدم برامج Java الأساليب الأصلية (مثل NIO ، NIO ، لم يتم تقديم أوصاف هنا) ، قد يتم تخصيص الذاكرة مباشرة خارج المنزل ، لكن مساحة الذاكرة الكلية محدودة ، ولن يكون هناك ذاكرة كافية ، وسيتم أيضًا إلقاء استثناء OutOfMemoryError .
2. الوصول إلى تخزين كائن مثيل
النقطة الأولى أعلاه لديها وصف عام للذاكرة في كل منطقة من الجهاز الظاهري. لكل منطقة ، هناك مشاكل في كيفية إنشاء البيانات ووضعها والوصول إليها. دعنا نستخدم ذاكرة الكومة الأكثر استخدامًا كمثال للحديث عن هذه الجوانب الثلاثة بناءً على نقطة ساخنة.
1. إنشاء كائن مثيل
عندما يقوم الجهاز الظاهري بتنفيذ تعليمات جديدة ، أولاً ، يحدد موقع مرجع رمز الفصل لكائن الإنشاء من التجمع الثابت ، ويحكم ما إذا كان قد تم تحميل الفصل وتهيئته. إذا لم يتم تحميله ، فسيتم تنفيذ عملية تهيئة تحميل الفئة (لن يتم تقديم الوصف هنا حول تحميل الفصل). إذا تعذر العثور على هذه الفئة ، فسيتم طرح استثناء شائع ClassNotFoundException ؛
بعد فحص تحميل الفصل ، يتم بالفعل تخصيص الذاكرة الفعلية (ذاكرة الكومة) للكائن. يتم تحديد مساحة الذاكرة المطلوبة بواسطة الكائن بواسطة الفئة المقابلة. بعد تحميل الفئة ، يتم إصلاح مساحة الذاكرة المطلوبة بواسطة كائن هذه الفئة ؛ تخصيص مساحة الذاكرة للكائن يعادل تقسيم قطعة من الكومة وتخصيصها لهذا الكائن ؛
وفقًا لما إذا كانت مساحة الذاكرة مستمرة (يتم تقسيم تخصيصها وغير المخصصة إلى جزأين كاملين) ، يتم تقسيمها إلى طريقتين لتخصيص الذاكرة:
1. الذاكرة المستمرة: يتم استخدام مؤشر كنقطة تقسيم بين الذاكرة المخصصة وغير المخصصة. يتطلب تخصيص ذاكرة الكائن فقط أن ينقل المؤشر حجم المساحة إلى مقطع الذاكرة غير المخصصة ؛ وتسمى هذه الطريقة "تصادم المؤشر".
2. الذاكرة المتقطعة: يحتاج الجهاز الظاهري إلى الحفاظ على (تسجيل) قائمة تسجل كتل الذاكرة هذه في الكومة غير المخصصة. عند تخصيص ذاكرة الكائن ، حدد مساحة ذاكرة ذات حجم مناسب لتخصيصها للكائن ، وتحديث هذه القائمة ؛ هذه الطريقة تسمى "القائمة الحرة".
سيواجه تخصيص ذاكرة الكائن أيضًا مشاكل التزامن. يستخدم الجهاز الظاهري حلين لحل مشكلة سلامة مؤشر الترابط هذه: أولاً ، استخدم CAS (المقارنة والتعيين)+ لتحديد وإعادة المحاولة لضمان ذرية عملية التخصيص ؛ ثانياً ، يتم تقسيم تخصيص الذاكرة إلى مساحات مختلفة وفقًا للمواضيع ، أي أن كل مؤشر ترابط تم تخصيصه مسبقًا من الذاكرة الخاصة باختصار مؤشر الترابط في الكومة ، تسمى المخزن المؤقت المحدد لخيط الخيط (TLAB) ؛ عندما يريد هذا الموضوع تخصيص الذاكرة ، يتم تخصيصه مباشرة من TLAB. فقط عندما يتم تخصيص TLAB للخيط بعد إعادة التخصيص ، يمكن تخصيص العملية المتزامنة من الكومة. هذا الحل يقلل بشكل فعال من تزامن ذاكرة تخصيص الكائن بين مؤشرات الترابط ؛ ما إذا كان الجهاز الظاهري يستخدم TLAB من خلال معلمة JVM -xx: +/- USETLAB.
بعد إكمال تخصيص الذاكرة ، بالإضافة إلى معلومات رأس الكائن ، يقوم الجهاز الظاهري بتهيئة مساحة الذاكرة المخصصة إلى قيمة صفر لضمان أن حقول مثيل الكائن يمكن استخدامها مباشرةً إلى القيمة صفر المقابلة لنوع البيانات دون تعيين قيم ؛ بعد ذلك ، قم بتنفيذ طريقة init لإكمال التهيئة وفقًا للرمز قبل إنشاء كائن مثيل ؛
2. تخطيط الكائنات في الذاكرة
في الجهاز الظاهري للنقطة الساخنة ، تنقسم الكائنات إلى ثلاثة أجزاء في الذاكرة: رأس الكائن وبيانات المثيل والمحاذاة والملء:
ينقسم رأس الكائن إلى جزأين: جزء منه يخزن بيانات وقت تشغيل الكائن ، بما في ذلك رمز التجزئة ، وعمر توليد مجموعة القمامة ، وحالة قفل الكائن ، قفل مؤشر الترابط ، معرف مؤشر ترابط متحيز ، طابع زمني متحيز ، إلخ ؛ في الأجهزة الظاهرية 32 بت و 64 بت ، يحتل هذا الجزء من البيانات 32 بت و 64 بت على التوالي ؛ نظرًا لوجود الكثير من بيانات وقت التشغيل ، لا يكفي 32 بت أو 64 بت لتخزين جميع البيانات تمامًا ، لذلك تم تصميم هذا الجزء لتخزين بيانات وقت التشغيل بتنسيق غير ثابت ، ولكنه يستخدم بتات مختلفة لتخزين البيانات وفقًا لحالة الكائن ؛ يقوم الجزء الآخر بتخزين مؤشر نوع الكائن ، مشيرًا إلى فئة هذا الكائن ، لكن هذا ليس ضروريًا ، ولا يلزم بالضرورة تحديد بيانات فئة الكائن باستخدام هذا الجزء من التخزين (سيتم مناقشته أدناه) ؛
بيانات المثيل هي محتوى أنواع مختلفة من البيانات المحددة بواسطة الكائن ، ولا يتم تخزين البيانات المحددة بواسطة هذه البرامج بالترتيب المحدد. يتم تحديدها بترتيب سياسات وتعريفات تخصيص الماكينة الافتراضية: طويلة/مزدوجة ، int ، قصيرة/شار ، بايت/منطقية ، OOP (كائن عادي ponint) ، يمكن أن نرى أن السياسات يتم تخصيصها وفقًا لعدد من العناصر النائمة من النوع ، وأن نفس الأنواع ستخصص الذاكرة معًا ؛ وفي ظل رضا هذه الشروط ، يسبق ترتيب متغيرات فئة الوالدين الفئة الفرعية ؛
جزء ملء الكائن لا يوجد بالضرورة. إنه يلعب دورًا في محاذاة العنصر النائب فقط. في نقطة ساخنة ، تتم إدارة إدارة الذاكرة الافتراضية في وحدات من 8 بايت. لذلك ، عندما يتم تخصيص الذاكرة ، لا يكون حجم الكائن مضاعفًا 8 ، ويتم إكمال ملء المحاذاة ؛
3. الوصول إلى الكائن <br /> في برنامج Java ، نقوم بإنشاء كائن ، وفي الواقع نحصل على متغير نوع مرجعي ، نقوم من خلاله بتشغيل مثيل في ذاكرة الكومة ؛ في مواصفات الجهاز الظاهري ، يتم النص على أن نوع المرجع هو مرجع يشير إلى الكائن ، ولا يحدد كيفية تحديد موقع هذا المرجع والوصول إلى الحالات في الكومة ؛ حاليًا ، في الأجهزة الافتراضية السائدة ، هناك طريقتان رئيسيتان لتنفيذ وصول الكائنات:
1. طريقة المقبض: يتم تقسيم المنطقة إلى ذاكرة كومة كبركة مقبض. يخزن المتغير المرجعي عنوان معطف الكائن ، ويخزن المقبض معلومات العنوان المحددة لكائن العينة ونوع الكائن. لذلك ، لا يمكن أن يحتوي رأس الكائن على نوع الكائن:
2. الوصول المباشر إلى المؤشر: يخزن النوع المرجعي مباشرة معلومات العنوان لكائن المثيل في الكومة ، ولكن هذا يتطلب أن يحتوي تخطيط كائن المثيل على نوع الكائن:
تتمتع هاتان طريقتين للوصول بمزاياههما الخاصة: عندما يتم تغيير عنوان الكائن (فرز الذاكرة ، مجموعة القمامة) ، لا يلزم تغيير متغير المرجع ، ولكن فقط يتم تغيير قيمة عنوان الكائن في المقبض ؛ أثناء استخدام طريقة الوصول المباشر للمؤشر ، يجب تعديل جميع مراجع هذا الكائن ؛ لكن طريقة المؤشر يمكن أن تقلل من عملية معالجة واحدة ، وفي حالة عدد كبير من الوصول إلى الكائن ، تكون مزايا هذه الطريقة أكثر وضوحًا ؛ يستخدم جهاز Hotspot Virtual طريقة الوصول المباشر للمؤشر هذه.
3. استثناء ذاكرة وقت التشغيل
هناك استثناءان رئيسيان قد يحدثان عند التشغيل في برنامج Java: OutOfMemoryerror و Stackoverflowerror ؛ ماذا سيحدث في منطقة الذاكرة؟ كما ذكرنا لفترة وجيزة من قبل ، باستثناء عداد البرنامج ، ستحدث مناطق الذاكرة الأخرى ؛ يوضح هذا القسم بشكل أساسي الاستثناءات في كل منطقة ذاكرة من خلال رمز المثيل ، وسيتم استخدام العديد من معلمات بدء تشغيل الجهاز الظاهري الشائع لشرح الموقف بشكل أفضل. (كيفية تشغيل البرنامج مع المعلمات غير موصوفة هنا)
1.
يحدث تدفق ذاكرة الكومة عند إنشاء الكائنات بعد أن تصل سعة الكومة إلى الحد الأقصى لسعة الكومة. في البرنامج ، يتم إنشاء الكائنات بشكل مستمر ويضمن هذه الكائنات جمع القمامة:
/** * معلمات الجهاز الظاهري: * -xms20m الحد الأدنى للسعة الكومة * -xmx20m سعة الكومة القصوى * author hwz * */class public headoutoToTofMemoryRor {public static void main (string [] args) ArrayList <headoutofmemoryerror> () ؛ بينما (صواب) {// إنشاء كائنات مستمرة وإضافتها إلى قائمة الحاويات TrowholdObj.add (new headoutofmemoryerror ()) ؛ }}} يمكنك إضافة معلمات الجهاز الظاهري :-XX:HeapDumpOnOutOfMemoryError . عند إرسال استثناء من OOM ، دع الجهاز الظاهري تفريغ ملف اللقطة للكومة الحالية. يمكنك استخدام مشكلة استثناء تجزئة الكلمات في المستقبل. لن يتم وصف هذا بالتفصيل. سأكتب مدونة لوصف بالتفصيل باستخدام أداة MAT لتحليل مشاكل الذاكرة.
2. مكدس الجهاز الظاهري ومكدس الطريقة المحلية
في الجهاز الظاهري للنقطة الساخنة ، لا يتم تنفيذ هاتين الأسلوبان معًا. وفقًا لمواصفات الجهاز الظاهري ، سيحدث هذان الاستثناءان في مجالات الذاكرة هذين:
1. إذا طلب مؤشر الترابط عمق المكدس أكبر من الحد الأقصى للعمق المسموح به من قبل الجهاز الظاهري ، فأرمي استثناء stackoverflowerror ؛
2. إذا لم يتمكن الجهاز الظاهري من التقدم بطلب للحصول على مساحة كبيرة للذاكرة عند توسيع مساحة المكدس ، فسيتم إلقاء استثناء OutOfMemoryError ؛
هناك بالفعل تداخل بين هاتين الموقتين: عندما لا يمكن تخصيص مساحة المكدس ، فهل من المستحيل التمييز بين ما إذا كانت الذاكرة صغيرة جدًا أو أن عمق المكدس المستخدم كبير جدًا.
استخدم طريقتين لاختبار الرمز
1. استخدم المعلمة -xss لتقليل حجم المكدس ، واتصل طريقة بشكل لا نهائي ، وزيادة عمق المكدس بلا حدود:
/** * معلمات الجهاز الظاهري: <br> * -XSS128K سعة المكدس * Author HWZ * */public class stackoverflowerror {private int stackdeep = 1 ؛ / *** عودة لا نهائية ، قم بتكبير عمق مكدس المكالمة*/ public void RecursiveInvoke () {StackDeep ++ ؛ متكرر () ؛ } public static void main (string [] args) {stackoverflowerror soe = new StackOverFlowerRor () ؛ حاول {soe.RecursiveInvoke () ؛ } catch (throwable e) {system.out.println ("stack deep =" + soe.stackdeep) ؛ رمي ه ؛ }}} يتم تعريف عدد كبير من المتغيرات المحلية في الطريقة ، ويسمى طول الجدول المتغير المحلي في مكدس الطريقة أيضًا بشكل لا نهائي:
/** * Author HWZ * */class public stackOomeError {private int stackdeep = 1 ؛ / *** تحديد عدد كبير من المتغيرات المحلية ، وزيادة الجدول المتغير المحلي في العودية اللانهائية* ، وزيادة بشكل لا نهائي من عمق المكدس*/ public void RecursiveInvoke () {double i ؛ مزدوج I2 ؛ //........ يتم حذف العدد الكبير من التعريفات المتغيرة هنا StackDeep ++ ؛ متكرر () ؛ } public static void main (string [] args) {stackOomeError soe = new StackOomeError () ؛ حاول {soe.RecursiveInvoke () ؛ } catch (throwable e) {system.out.println ("stack deep =" + soe.stackdeep) ؛ رمي ه ؛ }}}يوضح اختبار الرمز أعلاه أنه بغض النظر عما إذا كانت مكدس الإطار كبيرًا جدًا أو أن سعة الجهاز الظاهري صغير جدًا ، عندما لا يمكن تخصيص الذاكرة ، يتم إلقاء جميع stackoverflowerror ؛
3. منطقة الأسلوب وسباحة التجميع الثابتة في وقت التشغيل
هنا سنصف أولاً طريقة المتدرب للسلسلة: إذا كان تجمع السلسلة الثابت يحتوي بالفعل على سلسلة مساوية لكائن السلسلة هذا ، فسيقوم بإرجاع كائن سلسلة يمثل هذه السلسلة. خلاف ذلك ، أضف كائن السلسلة هذا إلى التجمع الثابت وإرجاع مرجع إلى كائن السلسلة هذا ؛ من خلال هذه الطريقة ، سيضيف بشكل مستمر كائن سلسلة إلى التجمع الثابت ، مما يؤدي إلى الفائض:
/** * معلمات الجهاز الظاهري: <br> * -xx: permsize = 10m حجم المنطقة الدائم * -xx: maxpermsize = 10m مساحة الحد الأقصى للمنطقة الدائمة * @author hwz * * ArrayList <String> () ؛ // استخدم طريقة string.intern لإضافة كائن التجمع الثابت لـ (int i = 1 ؛ true ؛ i ++) {list.add (string.valueof (i) .Intern ()) ؛ }}}ومع ذلك ، فإن رمز الاختبار هذا لا يتدفق أثناء تجمع وقت التشغيل الثابت في JDK1.7 ، ولكنه سيحدث في JDK1.6. لهذا السبب ، اكتب رمز اختبار آخر للتحقق من هذه المشكلة:
/** * تم اختبار طريقة string.intern تحت JDKs مختلفة * Author HWZ * */public class stringinterntest {public static void main (string [] args) {String str1 = new StringBuilder ("test"). oplend ("01"). toString () ؛ System.out.println (str1.intern () == Str1) ؛ String str2 = new StringBuilder ("test"). إلحاق ("02"). toString () ؛ system.out.println (str2.intern () == Str2) ؛ }} نتائج التشغيل تحت JDK1.6 هي: خطأ ، خطأ ؛
نتيجة الركض تحت JDK1.7 هي: صحيح ، صحيح ؛
اتضح أنه في jdk1.6 ، تقوم طريقة المتدرب () بنسخ مثيل السلسلة الأول الذي تمت مواجهته إلى الجيل الدائم ، والذي بدوره هو إشارة إلى المثيل في الجيل الدائم ، ومثيلات السلسلة التي أنشأتها StringBuilder في الكومة ، لذلك فهي ليست متساوية ؛
في JDK1.7 ، لا تقوم طريقة المتدرب () بنسخ المثيل ، ولكنها تسجل فقط مرجع الحالة الأولى التي تظهر في المجموعة الثابتة. لذلك ، فإن المرجع الذي تم إرجاعه بواسطة المتدرب هو نفسه المثيل الذي تم إنشاؤه بواسطة StringBuilder ، بحيث يعود صحيحًا ؛
لذلك ، لن يكون لرمز الاختبار لخطوط التجمع الثابت استثناء ثابت في التدفق ، ولكن قد يكون له استثناء من ذاكرة الكومة غير الكافية بعد الجري المستمر ؛
بعد ذلك ، تحتاج إلى اختبار الفائض في منطقة الطريقة ، فقط استمر في إضافة الأشياء إلى منطقة الطريقة ، مثل أسماء الفصول ، ومعدلات الوصول ، وحمامات ثابتة ، وما إلى ذلك. يمكننا السماح للبرنامج بتحميل عدد كبير من الفئات لملء منطقة الطريقة بشكل مستمر ، مما يؤدي إلى الفائض. نحن نستخدم CGLIB لمعالجة رمز bytecode مباشرة لإنشاء عدد كبير من الفئات الديناميكية:
/** * فئة اختبار الذاكرة في مجال الذاكرة * Author HWZ * */الفئة العامة methodareaoom {public static void main (string [] args) {// استخدم GCLIB لإنشاء فئات فرعية بلا حدود بينما (صحيح) {indancer ensancer = new ensancer () ؛ ensancer.SetSuperClass (maoomclass.class) ؛ ensancer.setuseCache (false) ؛ ensancer.setCallback (new methodInterceptor () {Override اعتراض الكائن العام (Object OBJ ، طريقة ، الكائن ، args ، args ، proxy methodproxy) يلقي رمي {return proxy.invokesuper (obj ، args) ؛}}) ؛ ensancer.create () ؛ }} static class maoomclass {}} من خلال مراقبة VisualVM ، يمكننا أن نرى أن عدد الفئات المحملة JVM يزداد في خط مستقيم مع استخدام pergen:
4. فائض الذاكرة المباشرة
يمكن ضبط حجم الذاكرة المباشرة من خلال معلمات الجهاز الظاهري : -xx: maxDirectMemorySize . لجعل تدفق الذاكرة المباشر ، تحتاج فقط إلى التقدم باستمرار بطلب للحصول على ذاكرة مباشرة. ما يلي هو نفس اختبار ذاكرة التخزين المؤقت للذاكرة المباشرة في Java Nio:
/** * معلمات الجهاز الظاهري: <br> * -xx: maxDirectMemorySize = 30M حجم الذاكرة المباشر * Author HWZ * */class public class DirectMemory {public static void main (string [] args) {list <Buffer> buffers = new ArrayList <abuster> () ؛ int i = 0 ؛ بينما (صحيح) {// طباعة النظام الحالي. out.println (++ i) ؛ // استهلاك الذاكرة المباشر عن طريق التقديم باستمرار للحصول على استهلاك الذاكرة المباشر في المخزن المؤقت لـ Cache Buffer.Add (bytebuffer.allocatedirect (1024*1024)) ؛ // المحاسبة 1M في كل مرة}}} في الحلقة ، في كل مرة يتم فيها تطبيق ذاكرة مباشرة 1M ، يتم تعيين الحد الأقصى للذاكرة المباشرة على 30 مترًا ، ويتم إلقاء استثناء عندما يعمل البرنامج 31 مرة: java.lang.OutOfMemoryError: Direct buffer memory
4. ملخص
ما سبق هو كل محتوى هذه المقالة. تصف هذه المقالة بشكل أساسي بنية تخطيط الذاكرة وتخزين الكائنات واستثناءات الذاكرة التي قد تحدث في مناطق الذاكرة المختلفة في JVM ؛ الكتاب المرجعي الرئيسي "الفهم المتعمق للجهاز الافتراضي Java (الإصدار الثاني)". إذا كان هناك أي خطأ ، فيرجى الإشارة إلى ذلك في التعليقات ؛ شكرا لدعمكم ل wulin.com.