منذ العمل ، تم كتابة المزيد والمزيد من الكود ، أصبح البرنامج أكثر فأكثر ، وأصبحت الكفاءة أقل فأكثر. هذا غير مسموح به على الإطلاق لمبرمج مثلي يتبع الكمال. لذلك ، بالإضافة إلى تحسين بنية البرنامج باستمرار ، أصبح تحسين الذاكرة وضبط الأداء "حيل" المعتادة.
لتحسين وضبط ذاكرة برامج Java والأداء ، من المؤكد أنه لا يمكن فهم المبادئ الداخلية للأجهزة الافتراضية (أو مواصفات أكثر صرامة). فيما يلي كتاب جيد "الجهاز الظاهري Java المتعمق (الإصدار الثاني)" (بقلم بيل فينرز ، ترجمته Cao Xiaogang و Jiang Jing. في الواقع ، هذا المقال هو الفهم الشخصي للمؤلف للآلات الافتراضية Java بعد قراءة هذا الكتاب). بالطبع ، لا تقتصر فوائد فهم الأجهزة الافتراضية Java على الفوائد المذكورة أعلاه. من منظور تقني أعمق ، سيكون فهم مواصفات وتنفيذ الأجهزة الافتراضية Java أكثر فائدة بالنسبة لنا لكتابة رمز Java فعال ومستقر. على سبيل المثال ، إذا فهمنا نموذج الذاكرة الخاص بجهاز Java Virtual وآلية إعادة تدوير الذاكرة للجهاز الظاهري ، فلن نعتمد كثيرًا عليه ، ولكننا سنحترم بشكل صريح من الذاكرة "عند الحاجة (رمز Java لا يمكن أن يحرر الذاكرة بشكل صريح ، ولكن يمكننا إبلاغ جامع القمامة بأن الكائن يحتاج إلى إعادة تدويره عن طريق إعادة تدوير الكائن). إذا فهمنا كيف يعمل مكدس Java ، فيمكننا تقليل خطر التدفق المكدس عن طريق تقليل عدد الطبقات العودية وعدد الحلقات. بالنسبة لمطوري التطبيقات ، قد لا تتضمن مباشرة عمل التنفيذ الأساسي لهذه الأجهزة الافتراضية Java ، ولكن فهم هذه المعرفة الأساسية سيكون لها تأثير دقيق وجيد على البرامج التي نكتبها.
ستشرح هذه المقالة بإيجاز نموذج العمارة والذاكرة لجهاز Java Virtual. إذا كان هناك أي كلمات غير لائقة أو تفسيرات غير دقيقة ، فيرجى التأكد من تصحيحها. يشرفني جدا!
بنية الجهاز الافتراضي جافا
النظام الفرعي تحميل فئة
هناك نوعان من اللوادر الفئة لأجهزة Java Virtual ، وهما محمل فئة بدء التشغيل والعملية المعرفة من قبل المستخدم.
يقوم الفصل الفرعي بتحميل الفصل في منطقة بيانات وقت التشغيل من خلال الاسم المؤهل بالكامل للفئة (اسم الحزمة واسم الفئة ، ويشمل Mount Network أيضًا عنوان URL). لكل نوع يتم تحميله ، يقوم جهاز Java Virtual بإنشاء مثيل لفئة java.lang.class لتمثيل النوع ، الذي يتم وضعه في منطقة الكومة في الذاكرة ، وتقع معلومات النوع المحملة في منطقة الطريقة ، وهي نفس جميع الكائنات الأخرى.
قبل تحميل النوع ، يجب ألا يحدد النظام الفرعي لتحميل الفئة تحديد ملف الفئة الثنائي المقابل واستيراده فحسب ، بل يتحقق أيضًا من صحة الفئة المستوردة ، وتخصيص الذاكرة وتهيئتها لمتغيرات الفئة ، ومراجع رمز التحليل كمراجع مباشرة. هذه الإجراءات هي بشكل صارم بالترتيب التالي:
1) التحميل - البحث وتحميل البيانات الثنائية من النوع ؛
2) الاتصال - أداء التحقق والإعداد والتحليل (اختياري)
3) تحقق لضمان صحة النوع المستورد
4) الاستعداد لتخصيص الذاكرة لمتغيرات الفصل وتهيئتها إلى القيم الافتراضية
5) تحليل المرجع الرمزي في النوع لتوجيه التطبيق
منطقة الطريقة
لكل نوع تم تحميله بواسطة النظام الفرعي لتحميل الفئة ، يحفظ الجهاز الظاهري البيانات التالية إلى منطقة الطريقة:
1. اسم النوع المؤهل بالكامل
2. اسم مؤهل تمامًا للنوع الفائق (java.lang.object لا يحتوي على فئة فائقة)
3. هو نوع الفئة A أو نوع واجهة
4. اكتب معدل الوصول
5. اسم أمر مؤهل بالكامل من أي hyperinterface المباشر
بالإضافة إلى معلومات النوع الأساسية أعلاه ، سيتم حفظ المعلومات التالية أيضًا:
6. اكتب حمام سباحة ثابت
7. معلومات الحقل (بما في ذلك اسم الحقل ، نوع الحقل ، معدل الحقل)
8. معلومات الطريقة (بما في ذلك اسم الطريقة ، ونوع الإرجاع ، والرقم ونوع المعلمات ، ومعدلات الطريقة. إذا لم تكن الطريقة مجردة ومحلية ، فسيتم أيضًا حفظ الطريقة الباخرة ومكدس المعامل وجدول الاستثناء للمنطقة المتغيرة المحلية في إطار مكدس الطريقة)
9. جميع متغيرات الفصل باستثناء الثوابت (في الواقع ، فهي متغيرات ثابتة للفئة. نظرًا لأن المتغيرات الثابتة يتم مشاركتها بواسطة جميع الحالات وترتبط مباشرة بالنوع ، فهي متغيرات على مستوى الفصل ويتم حفظها في منطقة الطريقة كأعضاء في الفصل)
10. إشارة إلى classloader
. إشارة إلى فئة الفئة // ستعيد السلسلة المرجعية. class من فئة الفئة المحفوظة للتو الآن ؛
لاحظ أنه يمكن أيضًا إعادة تدوير منطقة الطريقة بواسطة جامع القمامة.
كومة
يتم وضع جميع مثيلات الفصل أو المصفوفات التي تم إنشاؤها بواسطة برامج Java في وقت التشغيل في نفس الكومة ، ويحتوي كل جهاز Virtual Java أيضًا على مساحة كومة ، وتشترك جميع الخيوط في كومة (لهذا السبب سيؤدي برنامج Java متعدد الخيوط إلى حدوث مشاكل المزامنة في الوصول إلى الكائنات).
نظرًا لأن كل جهاز Virtual Java يحتوي على تطبيقات مختلفة لمواصفات الجهاز الظاهري ، فقد لا نعرف الشكل الذي يمثله كل جهاز Virtual Java مثيلات الكائنات في الكومة ، ولكن يمكننا الحصول على لمحة من خلال التطبيقات الممكنة التالية:
عداد البرنامج
لتشغيل برامج JAVA ، يحتوي كل مؤشر ترابط على سجله الخاص (عداد البرنامج) الخاص به ، والذي يتم إنشاؤه عند بدء مؤشر الترابط ، بحجم كلمة واحدة ، ويستخدم لحفظ موقع السطر التالي من التعليمات البرمجية التي يجب تنفيذها.
مكدس جافا
يحتوي كل مؤشر ترابط على مكدس Java ، والذي يحفظ حالة تشغيل الخيط بوحدات من إطارات المكدس. هناك نوعان من عمليات الأجهزة الافتراضية على مكدس Java: مكدس الضغط والتكديس ، وكلاهما لهما إطارات. يحفظ إطار المكدس البيانات مثل المعلمات الواردة ، والمتغيرات المحلية ، ونتائج التشغيل المتوسطة ، وما إلى ذلك ، والتي يتم عرضها عند اكتمال الطريقة ثم إصدارها.
ألقِ نظرة على لقطة الذاكرة لإطار المكدس عند إضافة متغيرين محليين معًا
كومة الطريقة المحلية
هذا هو المكان الذي تدعو فيه Java المكتبة المحلية لنظام التشغيل ، المستخدمة لتنفيذ JNI (Java Native Interface ، Java Local Interface)
محرك التنفيذ
يتحكم جوهر جهاز Java Virtual Machine في تحميل Java Bytecode and Racsing ؛ لتشغيل برامج Java ، كل مؤشر ترابط هو مثيل لمحرك تنفيذ الجهاز الظاهري المستقل. من البداية إلى نهاية دورة حياة الخيط ، يتم تنفيذ رمز Bytecode أو تنفيذ الأساليب المحلية.
واجهة محلية
متصل بمكدس الطريقة المحلية ومكتبة نظام التشغيل.
ملاحظة: تشير جميع الأماكن المذكورة في المقالة إلى "مواصفات الجهاز الظاهري Java لمنصات Javaee و Javase".
ممارسة تحسين ذاكرة الجهاز الظاهري
منذ ذكر الذاكرة ، يجب ذكر تسرب الذاكرة. كما نعلم جميعًا ، تم تطوير Java من أساس C ++ ، ومشكلة كبيرة مع برامج C ++ هي أنه من الصعب حل تسرب الذاكرة. على الرغم من أن JVM من Java لديها آلية لجمع القمامة الخاصة بها لإعادة تدوير الذاكرة ، في كثير من الحالات ، لا يحتاج مطورو برامج Java إلى القلق كثيرًا ، ولكن هناك أيضًا مشاكل تسرب ، وهي أصغر قليلاً من C ++. على سبيل المثال ، هناك كائن مشار إليه ولكن عديمة الفائدة في البرنامج: إذا كان البرنامج يشير إلى الكائن ، ولكنه لن يستخدمه أو لا يمكنه استخدامه في المستقبل ، فإن مساحة الذاكرة التي يستغرقها تضيع.
دعنا نلقي نظرة أولاً على كيفية عمل GC: مراقبة حالة تشغيل كل كائن ، بما في ذلك التطبيق ، والاستشهاد ، والاستشهاد ، والتعيين ، وما إلى ذلك ، عندما لم يعد الكائن يتم ذكره ، حرر الكائن (لم يتم شرح تركيز هذه المقالة كثيرًا). يعتمد العديد من مبرمجي Java كثيرًا على GC ، ولكن مفتاح المشكلة هو أنه بغض النظر عن مدى جودة آلية جمع القمامة JVM ، فإن الذاكرة هي دائمًا مورد محدود. لذلك ، حتى إذا ستقوم GC بإكمال معظم مجموعة القمامة بالنسبة لنا ، فلا يزال من الضروري الانتباه إلى تحسين الذاكرة أثناء عملية الترميز بشكل مناسب. هذا يمكن أن يقلل بشكل فعال من عدد GCs ، مع تحسين استخدام الذاكرة ، وزيادة كفاءة البرنامج.
بشكل عام ، يجب أن يبدأ تحسين ذاكرة الأجهزة الافتراضية Java من جانبين: Java Virtual Machines وتطبيقات Java. يشير السابق إلى التحكم في حجم قسم الذاكرة المنطقي للماكينة الظاهرية من خلال معلمات الجهاز الظاهري وفقًا لتصميم التطبيق بحيث تكمل ذاكرة الجهاز الظاهري متطلبات ذاكرة البرنامج ؛ يشير الأخير إلى تحسين خوارزميات البرنامج ، وتقليل عبء GC ، وتحسين معدل نجاح إعادة تدوير GC.
المعلمات لتحسين ذاكرة الجهاز الظاهري من خلال المعلمات هي كما يلي:
XMS
حجم الكومة الأولي
XMX
جافا كومة أقصى قيمة
1mn
حجم كومة الجيل الشاب
XSS
حجم المكدس لكل موضوع
ما سبق ثلاثة معلمات أكثر شيوعًا ، بعضها:
xx: minheapfreeratio = 40
الحد الأدنى من النسبة المئوية من كومة خالية بعد GC لتجنب التوسع.
xx: maxheapfreeratio = 70
أقصى نسبة من الكومة خالية بعد GC لتجنب الانكماش.
xx: Newratio = 2
نسبة أحجام الجيل الجديد/القديم. [Sparc -Client: 8 ؛ x86 -Server: 8 ؛ x86 -client: 12.] -العميل: 8 (1.3.1+) ، x86: 12]
XX: NEWSIZE = 2.125M
الحجم الافتراضي للجيل الجديد (بالبايت) [5.0 وأحدث: يتم تحجيم VMs 64 بت 30 ٪ أكبر ؛ x86: 1M ؛ x86 ، 5.0 وما فوق: 640K]
xx: maxNewSize =
الحد الأقصى لحجم الجيل الجديد (في بايت). منذ 1.4 ، يتم حساب MaxNewSize كدالة لـ Newratio.
xx: Survivorratio = 25
نسبة مساحة مساحة Eden/Survivor [SPARC في 1.3.1: 25 ؛ منصات سولاريس الأخرى في 5.0 وأقدم: 32]
xx: permsize =
الحجم الأولي للجيل الدائم
xx: maxpermsize = 64m
حجم الجيل الدائم. [5.0 وأحدث: يتم تحجيم 64 بت VMs بنسبة 30 ٪ ؛ 1.4 AMD64: 96M ؛ 1.3.1 -client: 32m.]
ما هو مذكور أدناه لتحسين استخدام الذاكرة وتقليل مخاطر الذاكرة من خلال تحسين خوارزميات البرنامج أمر تجريبي تمامًا وهو للرجوع إليه فقط. إذا كان هناك أي inappropropriated ، فالرجاء تصحيحني ، شكرًا لك!
1. حرر مرجع الكائنات عديمة الفائدة في أقرب وقت ممكن (xx = null ؛)
انظر إلى قطعة رمز:
القائمة العامة <Pagedata> parse (صفحة htmlpage) {list <Pagedata> list = null ؛ حاول {list valuelist = page.getByxPath (config.getContEntxPath ()) ؛ if (valuelist == null || valuelist.isempty ()) {return list ؛ } // قم بإنشاء كائن عند الحاجة ، احفظ الذاكرة وتحسين قائمة الكفاءة = ArrayList جديد <Pagedata> () ؛ Pagedata pagedata = new pagedata () ؛ stringBuilder value = new StringBuilder () ؛ لـ (int i = 0 ؛ i <valuelist.size () ؛ i ++) {htmlelement content = (htmlelement) valuelist.get (i) ؛ domnodelist <htmlelement> imgs = content.getElementSbyTagName ("img") ؛ if (imgs! = null &&! imgs.isempty ()) {for (htmlelement img: imgs) {try {htmlimage image = (htmlimage) img ؛ string path = image.getSrCattribute () ؛ تنسيق السلسلة = path.substring (path.lastindexof (".") ، path.length ()) ؛ String localPath = "D:/Images/" + Md5Helper.md5 (path) .replace ("//" ، "). استبدال ("/"،") + تنسيق ؛ ملف localfile = ملف جديد (localPath) ؛ if (! localfile.exists ()) {localfile.createNewFile () ؛ Image.saveas (localfile) ؛ } image.setattribute ("SRC" ، "file: ///" + localpath) ؛ localfile = null ؛ صورة = خالية ؛ IMG = فارغة ؛ } catch (استثناء e) {}} // لن يتم استخدام هذا الكائن في المستقبل. إن تطهير الإشارة إلى ذلك يعادل إخبار GC مقدمًا. يمكن للكائن إعادة تدوير IMGs = فارغة ؛ } string text = content.asxml () ؛ value.append (text) .Append ("<br/>") ؛ valuelist = null ؛ المحتوى = فارغ ؛ النص = فارغ ؛ } pagedata.setContent (value.toString ()) ؛ pagedata.setcharset (page.getPageenCoding ()) ؛ list.add (pagedata) ؛ // pagedata = null ؛ لا جدوى لأن القائمة لا تزال تحتفظ بالإشارة إلى الكائن ، ولن تقوم GC بإعادة تدوير قيمة IT = NULL ؛ // لا توجد قائمة = فارغة هنا ؛ لأن القائمة هي قيمة الإرجاع للطريقة ، وإلا فإن قيمة الإرجاع التي تحصل عليها من الطريقة ستكون دائمًا فارغة ، وهذا النوع من الخطأ ليس من السهل اكتشاف أو استبعاد} catch (استثناء e) {} قائمة الإرجاع ؛ }2. استخدم أنواع بيانات المجموعة بعناية ، مثل المصفوفات والأشجار والرسوم البيانية والقوائم المرتبطة بهياكل البيانات الأخرى. هياكل البيانات هذه أكثر تعقيدًا لإعادة تدوير GC.
3. تجنب التقديم بشكل صريح لمساحة الصفيف. عندما يتعين عليك التقديم بشكل صريح ، حاول تقدير قيمتها المعقولة قدر الإمكان.
4. حاول تجنب إنشاء وتهيئة عدد كبير من الكائنات في المُنشئ الافتراضي للفئة ، ومنع النفايات غير الضرورية لموارد الذاكرة عند استدعاء مُنشئها الخاص بالفئة.
5. حاول تجنب النظام القسري لإعادة تدوير ذاكرة القمامة ، وزيادة الوقت النهائي لإعادة تدوير القمامة في النظام
6. حاول استخدام متغيرات القيمة الفورية عند تطوير تطبيقات استدعاء الطريقة عن بُعد ، ما لم يكن المتصل عن بُعد يحتاج إلى الحصول على قيمة متغير القيمة الفوري.
7. حاول استخدام تقنية تجميع الكائنات في السيناريوهات المناسبة لتحسين أداء النظام