الرسم البياني للهيكل الداخلي JVM
يتم تقسيم الأجهزة الافتراضية Java بشكل رئيسي إلى خمسة مناطق: منطقة الطريقة ، الكومة ، مكدس Java ، سجل الكمبيوتر ، ومكدس الطريقة المحلية. دعونا نلقي نظرة على بعض القضايا المهمة حول بنية JVM.
1. ما هي المناطق التي يتم مشاركتها؟ أي منها خاص؟
يتم إنشاء وتدمير مكدس Java ومكدس الطريقة المحلية وعداد البرنامج مع بدء وتنتهي مؤشر ترابط المستخدم.
كل موضوع لديه هذه المجالات المستقلة. تتم مشاركة منطقة الطريقة والكومة من قبل جميع مؤشرات الترابط في عملية JVM بأكملها.
2. ما الذي يتم حفظه في منطقة الطريقة؟ هل سيتم إعادة تدويره؟
لا يتم حفظ منطقة الطريقة فقط المعلومات والرمز. في الوقت نفسه ، في منطقة فرعية تسمى مجموعة وقت التشغيل ، المراجع الرمزية المختلفة في الجدول الثابت في ملف الفئة ، وكذلك المراجع المباشرة المترجمة. يتم الوصول إلى هذه المعلومات من خلال كائن فئة في الكومة كواجهة.
على الرغم من تخزين معلومات النوع في منطقة الطريقة ، إلا أنه سيتم إعادة تدويرها أيضًا ، لكن ظروف إعادة التدوير صارمة نسبيًا:
(1) تم إعادة تدوير جميع مثيلات هذه الفئة
(2) تم إعادة تدوير تحميل classloader هذا الفئة
(3) لم يتم الرجوع إلى كائن الفئة من هذه الفئة في أي مكان (بما في ذلك الوصول إلى الانعكاس الفئة)
3. هل محتوى المسبح الثابت في منطقة الطريقة دون تغيير؟
يقوم تجمع وقت التشغيل في منطقة الطريقة بحفظ البيانات في تجمع ثابت ثابت في ملف الفئة. بالإضافة إلى تخزين مختلف المراجع الحرفية والرمزية التي تم إنشاؤها في وقت الترجمة ، فإنه يحتوي أيضًا على المراجع المباشرة المترجمة. ولكن هذا لا يعني أن التجمع الثابت لن يتغير أثناء وقت التشغيل. على سبيل المثال ، عند التشغيل ، يمكنك الاتصال بالطريقة المتدربة في السلسلة ووضع ثوابت سلسلة جديدة في المجمع.
حزمة com.cdai.jvm ؛ الفئة العامة RunTimeConstantPool {public static void main (string [] args) {String S1 = new String ("Hello") ؛ السلسلة S2 = سلسلة جديدة ("Hello") ؛ System.out.println ("قبل المتدرب ، S1 == S2:" + (S1 == S2)) ؛ s1 = s1.intern () ؛ s2 = s2.intern () ؛ System.out.println ("بعد المتدرب ، S1 == S2:" + (S1 == S2)) ؛ }}
4. هل كل حالات الكائن مخصصة على الكومة؟
من خلال النضج التدريجي لتكنولوجيا تحليل الهروب ، جعلت تقنيات التخصيص على الإقامة والاستبدال العدسي "جميع الكائنات المخصصة على الكومة" أقل مطلقًا.
يعني ما يسمى الهروب أنه عندما يتم الرجوع إلى مؤشر الكائن بطرق أو مؤشرات ترابط متعددة ، فإننا نقول أن هذا المؤشر يهرب.
بشكل عام ، يتم تخصيص كائنات Java في الكومة ، ويتم حفظ مؤشرات الكائن فقط في المكدس. على افتراض أن المتغير المحلي لا يهرب أثناء تنفيذ الطريقة (يتعرض للخارج من الطريقة) ، سيتم تخصيصه مباشرة على المكدس ثم يستمر في تنفيذها في مكدس المكالمات. بعد تنفيذ الطريقة ، يتم إعادة تدوير مساحة المكدس وإعادة تدوير المتغيرات المحلية أيضًا. هذا يقلل من تخصيص عدد كبير من الكائنات المؤقتة في الكومة ويحسن كفاءة إعادة تدوير GC.
بالإضافة إلى ذلك ، سيحذف تحليل الهروب أيضًا الأقفال على المتغيرات المحلية التي لم تفلت ، وتجاهل الأقفال المملوكة لهذا المتغير.
عند تمكين طريقة تحليل الهروب ، أضف معلمات بدء تشغيل JVM: -xx: +doNCapeanalysis؟ Escapeanalysistist.
5. كم عدد الطرق للوصول إلى الكائنات على الكومة؟
(1) الوصول المباشر إلى المؤشر
يحفظ المرجع على المكدس مؤشرًا إلى كائن على الكومة ، ويمكنك تحديد موقع الكائن في GO ، والذي يتمتع بسرعة وصول أسرع.
ومع ذلك ، عندما يتم نقل الكائنات في الكومة (غالبًا ما يتم نقل كل كائن أثناء جمع القمامة) ، يجب أيضًا تغيير قيمة متغير المؤشر على المكدس. حاليًا ، تتبنى نقطة الساخنة JVM هذه الطريقة.
(2) الوصول غير المباشر إلى التعامل مع
يشير المرجع على المكدس إلى مقبض في تجمع المقبض ، ويتم الوصول إلى الكائن من خلال القيمة في هذا المقبض. لذلك ، يشبه المقبض مؤشرًا ثانويًا ، والذي يتطلب وضعين للوصول إلى الكائن ، وهو أبطأ من وضع المؤشر المباشر ، ولكن عندما يتحرك الكائن في الكومة ، فلا داعي لتغيير القيمة المشار إليها على المكدس.
كيف تتفوق على JVM
بعد فهم دور مناطق الذاكرة الخمسة في الأجهزة الافتراضية Java ، دعونا نستمر في التعلم في ظل الظروف التي ستغلب عليها هذه المناطق.
1. تكوين معلمة الجهاز الظاهري
-xms: حجم الكومة الأولي ، الافتراضي هو 1/64 من الذاكرة الفعلية (<1 جيجابايت) ؛ الافتراضي (يمكن ضبط المعلمة minheapfreeratio) عندما تكون ذاكرة الكومة الحرة أقل من 40 ٪ ، ستزيد JVM من الكومة حتى الحد الأقصى لـ -xmx.
-xmx: الحد الأقصى لحجم الكومة ، الافتراضي (يمكن تعديل معلمة maxheapfreeratio) عندما تكون ذاكرة الكومة الحرة أكبر من 70 ٪ ، فإن JVM سوف يقلل من الكومة حتى الحد الأدنى للـ xms.
-XSS: حجم المكدس لكل موضوع. بعد JDK5.0 ، يكون حجم المكدس لكل مؤشر ترابط 1M ، وفي الماضي ، يكون حجم مكدس كل مؤشر ترابط 256 ألفًا. يجب تعديله بشكل مناسب وفقًا لحجم الذاكرة المطلوب لخيط التطبيق. في نفس الذاكرة الفعلية ، يمكن أن يؤدي تقليل هذه القيمة إلى إنشاء المزيد من المواضيع. ومع ذلك ، لا يزال لدى نظام التشغيل حد لعدد المواضيع في عملية ما ولا يمكن إنشاؤه بلا حدود ، مع قيم الخبرة التي تتراوح بين حوالي 3000 إلى 5000. تطبيقات صغيرة بشكل عام ، إذا لم تكن المكدس عميقة للغاية ، يجب أن تكون 128 كيلو كافية. للتطبيقات الكبيرة ، يوصى 256K. هذا الخيار له تأثير كبير على الأداء ويتطلب اختبارًا صارمًا.
-xx: permsize: اضبط القيمة الأولية للجيل الدائم (PERM Gen). القيمة الافتراضية هي 1/64 من الذاكرة الفعلية.
-xx: maxpermsize: اضبط القيمة القصوى للجيل المستمر. 1/4 من الذاكرة الفعلية.
2. فائض منطقة الطريقة
نظرًا لأن منطقة الطريقة تحمل المعلومات ذات الصلة للفئة ، عندما نقوم بتحميل العديد من الفئات ، فإن منطقة الطريقة ستتفوق عليها. نحاول هنا أن نتفوق على منطقة الطريقة من خلال طريقتين: الوكيل الديناميكي JDK وكيل CGLIB.
2.1 الوكيل الديناميكي JDK
حزمة com.cdai.jvm.overflow ؛ استيراد java.lang.reflect.invocationHandler ؛ استيراد java.lang.reflect.method ؛ استيراد java.lang.reflect.proxy ؛ الفئة العامة methodareaoverflow {static interface OomiInterface {} الفئة الثابتة oomobject تنفذ OomiInterface {} فئة ثابتة OOMOBject2 تنفذ OOMIINTERFACE {} public static void main (string [] args) {Final OomObject Object = new OOMOBJECT () ؛ بينما (صواب) {OomiInterface proxy = (OomiInterface) proxy.newproxyinstance (thread.currentTherThread (). System.out.println ("interceptor1 يعمل") ؛ system.out.println (proxy.getClass ()) ؛ System.out.println ("proxy1:" + proxy) ؛ OomiInterface proxy2 = (OomiInterface) proxy.newproxyinstance (thread.currentThread (). System.out.println ("interceptor2 يعمل") ؛ System.out.println (proxy2.getClass ()) ؛ System.out.println ("proxy2:" + proxy2) ؛ }}} على الرغم من أننا نستمر في استدعاء طريقة proxy.newinstance () لإنشاء فئة الوكيل ، فإن JVM لا يحتوي على تدفق ذاكرة.
تقوم كل مكالمة بإنشاء مثيل مختلف لفئة الوكيل ، لكن كائن الفئة من فئة الوكيل لم يتغير. هل هو وكيل
هل لدى الفصل ذاكرة التخزين المؤقت لكائن فئة من فئة الوكيل؟ سيتم تحليل الأسباب المحددة بالتفصيل في "JDK Dynamic Proxy و CGLIB" اللاحقة.
2.2 وكيل CGLIB
سيقوم CGLIB أيضًا بتخزين كائن الفئة من فئة الوكيل ، ولكن يمكننا تكوينه لعدم ذاكرة التخزين المؤقت لكائن الفئة.
وبهذه الطريقة ، يمكن تحقيق الغرض من التغلب على منطقة الطريقة عن طريق إنشاء فصول وكيل بشكل متكرر.
حزمة com.cdai.jvm.overflow ؛ استيراد java.lang.reflect.method ؛ استيراد net.sf.cglib.proxy.enhancer ؛ استيراد net.sf.cglib.proxy.methodinterceptor ؛ استيراد net.sf.cglib.proxy.methodproxy ؛ الفئة العامة methodareaoverflow2 {static class oomobject {} public static void main (string [] args) {بينما (true) {ensancer ensancer = new ensancer () ؛ ensancer.SetSuperClass (oomobject.class) ؛ ensancer.setuseCache (false) ؛ ensancer.setCallback (new methodInterceptor () {Override public intercept (Object OBJ ، method ، object [] args ، methodproxy proxy) يلقي رمي {return method.invoke (obj ، args) ؛}}) ؛ proxy oomobject = (oomobject) ensancer.create () ؛ system.out.println (proxy.getClass ()) ؛ }}}
3
فائض الكومة بسيط نسبيا. يمكنك تجاوز الكومة عن طريق إنشاء كائن صفيف كبير.
حزمة com.cdai.jvm.overflow ؛ الطبقة العامة Heapoverflow {private static Final int mb = 1024 * 1024 ؛ suppresswarnings ("غير مستخدمة") الفراغ الثابت العام (سلسلة [] args) {byte [] bigmemory = new byte [1024 * mb] ؛ }}
4. مكدس فائض
فائض المكدس هو أيضا شائع. في بعض الأحيان عندما لا يكون للمكالمة العودية التي نكتبها حالة الإنهاء الصحيحة ، ستستمر الطريقة في التكرار ، وسيستمر عمق المكدس في الزيادة ، وفي النهاية سيحدث سعة المكدس.
حزمة com.cdai.jvm.overflow ؛ الطبقة العامة stackoverflow {private static int stackDepth = 1 ؛ stackOverflow static static static static () {stackDepth ++ ؛ stackoverflow () ؛ } public static void main (string [] args) {try {stackoverflow () ؛ } catch (استثناء e) {system.err.println ("عمق المكدس:" + stackDepth) ؛ E.PrintStackTrace () ؛ }}}