يمكنك في كثير من الأحيان رؤية مناقشات حول تخصيص ذاكرة سلاسل Java عند التشغيل في أقسام رئيسية على الإنترنت ، مثل: String A = "123" ، السلسلة B = سلسلة جديدة ("123") ، أين يتم تخزين هذين النموذجين من الأوتار؟ في الواقع ، لا يتم تخزين القيمة الحرفية للسلسلة "123" في هذين النموذجين على المكدس ولا على الكومة في وقت التشغيل. يتم تخزينها في منطقة ثابتة معينة في منطقة الطريقة ، ويتم الاحتفاظ بنسخة واحدة فقط في الذاكرة لنفس القيمة الحرفية للسلسلة. سنقوم بتحليله مع أمثلة أدناه.
1. يتم استخدام المشغل == في حالتين حيث تتم مقارنة مقارنتين مرجعتين للسلسلة:
فئة عامة stringtest {public static void main (string [] args) {// part 1 string s1 = "i love China" ؛ السلسلة S2 = "أنا أحب الصين" ؛ System.out.println ("النتيجة:" + S1 == S2) ؛ // نتيجة تشغيل البرنامج هي true // الجزء 2 السلسلة S3 = سلسلة جديدة ("أنا أحب الصين") ؛ السلسلة S4 = سلسلة جديدة ("أنا أحب الصين") ؛ System.out.println ("النتيجة:" + S3 == S4) ؛ // نتيجة تشغيل البرنامج خاطئة}}نحن نعلم أن المشغل == في Java يقارن قيم المتغيرات. تخزن قيمة المتغير المقابل لنوع المرجع عنوان الكائن المرجعي. هنا هي نوع المرجع ، وقيم المتغيرات الأربعة هنا بالفعل تخزين عناوين السلسلة. تعتبر نتيجة التنفيذ لـ PIRT2 واضحة ، لأن المشغل الجديد سيؤدي إلى إنشاء JVM كائنات جديدة في الكومة في وقت التشغيل ، وتختلف عناوين الكائنين المختلفين. ومع ذلك ، من نتيجة التنفيذ في Part1 ، يمكن ملاحظة أن S1 و S2 هما نفس العنوان المشار إليه. إذن ، أين يتم تخزين الأوتار التي أشار إليها المتغيرات S1 و S2؟ كيف تتعامل JVM مع السلاسل؟ وبالمثل ، بالنسبة لكائنات سلسلة مختلفة في الكومة ، أشارت المتغيرات S3 و S4 ، هل سيوفرون سلسلة "أنا أحب الصين" في مساحة كائنهم؟ من أجل فهم كيفية تعامل JVM مع السلاسل ، ننظر أولاً إلى تعليمات Bytecode التي تم إنشاؤها بواسطة برنامج التحويل البرمجي Java. من خلال تعليمات Bytecode ، نقوم بتحليل العمليات التي سيتم تنفيذها بواسطة JVM.
2. فيما يلي بعض المعلومات bytecode التي تم إنشاؤها بواسطة البرنامج. يمثل الأحمر الجزء الذي نحتاجه إلى الانتباه إليه.
مجموعة ثابتة: #1 = Class #2 // StringTest #2 = UTF8 StringTest #3 = Class #4 // Java/Lang/Object #4 = Utf8 Java/Lang/Object #5 = Utf8 <Ing> #6 = utf8 () #5: #6 // "<ING>" :() V #10 = UTF8 LYNENUMBERTABLE #11 = UTF8 LOCALVARIOBLETable #12 = UTF8 This #13 = UTF8 LSTRINGTEST ؛ #14 = UTF8 MAIN #15 = UTF8 ([ljava/lang/string ؛) v #16 = String #17 // I Love China Reference to string address #17 = utf8 i love China #18 = FieldRef #19. #21 // Java/Lang/System.out: ljava/io/printstream ؛ #19 = Class #20 // Java/Lang/System #20 = Utf8 Java/Lang/System #21 = NameAndType #22: #23 // Out: ljava/io/printstream ؛ #22 = UTF8 OUT #23 = UTF8 LJAVA/IO/printStream ؛ #24 = الفئة #25 // java/lang/stringbuilder #25 = utf8 java/lang/stringbuilder #26 = string #27 // النتيجة: #27 = utf8 النتيجة: #28 = methodref #24. #29 // java/lang/stringbuilder. ] إلحاق: (z) ljava/lang/stringBuilder ؛#33 = UTF8 إلحاق#34 = UTF8 (z) ljava/lang/stringbuilder ؛#35 = methodRef#24.#36 // java/lang/stringbuilder.toString :() ljava/lang/string ؛ toString :() ljava/lang/string ؛#37 = Utf8 toString#38 = UTF8 () ljava/lang/string ؛#39 = methodref#40.#42 // java/io/printstream.println: (ljava/lang/string ؛) java/io/printstream#42 = nameandtype#43:#30 // println: (ljava/lang/string ؛) v#43 = utf8 println#44 = class#45 // java/lang/string#45 = utf8 java/lang/string#46 = java/lang/string. StackMaptable#55 = Class#48 // "[Ljava/Lang/String ؛"#56 = UTF8 SourceFile#57 = UTF8 StringTest.java ............ // يتم شرح تعليمات Bytecode للطريقة المقابلة وتنفيذها بواسطة وقت تشغيل JVM. الفراغ الثابت العام الرئيسي (java.lang.string []) ؛ واصف: ([ljava/lang/string ؛) V أعلام: acc_public ، acc_static رمز: stack = 4 ، السكان المحليين = 5 ، args_size = 1 0: ldc #16 // string أنا أحب الصين ، يشير هذا التوجيه إلى الرمز في #16 من المجموعة الثابتة ، وهنا يدفع السلسلة "Ilove China" إلى أعلى المكدس. تتوافق هذه التعليمات مع التعليمات التالية 2. السلسلة S1 = "I Love China" 2 في البرنامج: store_1 // قم بتعيين مرجع الكائن في الجزء العلوي من المكدس إلى المتغير المحلي 1. 3: LDC #16 // String I Love China ، فإن التعليمات في نفس 0 تشير إلى ثابت في مرجع الرمز نفسه. تتوافق هذه التعليمات والتعليمات التالية 5 مع سلسلة S2 = "I Love China" في البرنامج. 5: 9: جديد #24 // class java/lang/stringbuilder 12: DUP 13: LDC #26 // السلسلة النتيجة: 15: invokespecial #28 // method java/lang/stringbuilder. لمقارنة ما إذا كانت متساوية أم لا ، انتقل إلى التوجيه 27 ، تنفيذ ، تنفيذ التعليمات التالية على قدم المساواة 23: ICONST_1 24: GOTO 28 27: ICONST_0 28: InvokeVirtual #31 // Method Java/Lang/StringBuilder.Append: (Z) ljava/lang/stringbuilder ؛ 31: invokevirtual #35 // الطريقة java/lang/stringBuilder.toString :() ljava/lang/string ؛ 34: InvokeVirtual #39 // الطريقة java/io/printstream.println: (ljava/lang/string ؛) v 37: جديد #44 // class java/lang/string ، إنشاء كائن ، والذي يقع في مرجع البركة الثابت #44 ، هنا هو كائن السلسلة ، والمرجعية للكائنات مسبقة إلى أعلى. 40: DUP // انسخ نسخة من الكائن الموجود في الجزء العلوي من المكدس وادفعه إلى أعلى المكدس. 41: LDC #16 // String I Love China ، مثل 0 ، 3 تعليمات. 43: Invokespecial #46 // الطريقة java/lang/string. 53: InvokEspecial #46 // method java/lang/string. java/lang/system.out: ljava/io/printstream ؛ 61: جديد #24 // Class Java/Lang/StringBuilder 64: DUP 65: LDC #26 // السلسلة النتيجة: 67: vokespecial #28 // method java/lang/stringbuilder.append: (z) ljava/lang/stringbuilder ؛ 84: invokevirtual #35 // الطريقة java/lang/stringbuilder.toString :() ljava/lang/stringbuilder.append: (z) ljava/lang/stringbuilder ؛ 84: invokevirtual #35 // method java/lang/stringbuilder.toString :() ljava/lang/stringBuilder ؛ 87: [ljava/lang/string ؛ // المتغير المحلي 088 1 S1 ljava/lang/string ؛ // متغير محلي 185 2 S2 LJAVA/LANG/String ؛ // المتغير المحلي 244 3 S3 LJAVA/LANG/String ؛ // متغير محلي 333 4 S4 LJAVA/LANG/String ؛ // متغير محلي 4
يرتبط الجزء الأحمر من رمز bytecode بمناقشتنا. من خلال رمز Bytecode الذي تم إنشاؤه ، يمكننا استخلاص الاستنتاجات التالية لبرنامج المثال.
1). عندما يقوم برنامج التحويل البرمجي Java بتجميع البرنامج في Bytecode ، تحدد السلسلة الثابتة "I Love China" أولاً ما إذا كان موجودًا في تجمع Bytecode الثابت. إذا لم يكن موجودًا ، فلن ينشئه. وهذا هو ، سلسلة متساوية محفوظة. يمكن العثور على نسخة واحدة فقط من خلال المراجع الرمزية ، بحيث تشير متغيرات السلسلة S1 و S2 في البرنامج إلى نفس السلسلة الثابت في التجمع الثابت. في وقت التشغيل ، ستقوم JVM بتخزين ثوابت السلسلة في حمام السباحة الثابتة من رمز Bytecode في موقع منطقة الطريقة التي تسمى عادةً حمام السباحة الثابت ، ويتم الوصول إلى السلسلة من خلال الفهارس في شكل صفيف أحرف. يشير JVM إلى العنوان المرجعي النسبي للسلسلة التي أشار إليها S1 و S2 إلى عنوان الذاكرة الفعلي للسلسلة في وقت التشغيل.
2). بالنسبة للسلسلة S3 = سلسلة جديدة ("أنا أحب الصين") ، السلسلة S4 = سلسلة جديدة ("أنا أحب الصين") ، من رمز Bytecode ، يمكن رؤية أنه يدعو التعليمات الجديدة. سيقوم JVM بإنشاء كائنين مختلفين في وقت التشغيل ، ويشير S3 و S4 إلى عناوين كائن مختلفة. لذلك ، فإن نتيجة المقارنة S3 == S4 خاطئة.
ثانياً ، بالنسبة لتهيئة كائنات S3 و S4 ، يُرى من رمز bytecode أن طريقة init للكائن تسمى ومرجع "أنا أحب الصين" في التجمع الثابت. إذن ما هو إنشاء كائن السلسلة والتهيئة؟ يمكننا التحقق من التعليمات البرمجية المصدر للسلسلة و bytecode التي تم إنشاؤها بواسطة كائن السلسلة لفهم ما إذا كانت السلسلة يتم نسخها داخل الكائن أو المرجع مباشرة إلى عنوان التجمع الثابت المقابل للسلسلة.
3. جزء من الكود المصدري لكائن السلسلة:
<span style = "font-size: 14pt"> سلسلة الفئة النهائية العامة تنفذ java.io.serializable ، قابلة للمقارنة <string> ، charsevenence { /** يتم استخدام القيمة لتخزين الأحرف. */ قيمة char النهائية الخاصة [] ؛ / ** ذاكرة التخزين المؤقت رمز التجزئة للسلسلة*/ private int. // الافتراضي إلى 0 public string () {this.value = new char [0] ؛ } </span> <span style = "background-color: #ffffff ؛ font-size: 18pt"> public string (String intern) {this.value = Original.value ؛ this.hash = Original.hash ؛ } </span>من الرمز المصدر ، نرى أن هناك قيمة char متغير مثيل [] في فئة السلسلة. من خلال طريقة البناء ، يمكننا أن نرى أن الكائن لا يقوم بعمليات النسخ عند التهيئة ، ولكن يعين فقط مرجع العنوان لكائن السلسلة الذي تم تمريره إلى القيمة المتغيرة للمثول. من هذا ، يمكننا في البداية أن نستنتج أنه حتى عند إنشاء كائن سلسلة باستخدام سلسلة جديدة ("ABC") ، يتم تخصيص المساحة للكائن في كومة الذاكرة ، ولكن لا يتم تخزين أي معلومات حول "ABC" نفسها على الكومة ، ولكن يتم تهيئة الإشارة إلى سلسلة "ABC" داخل متغير مثيلها إلى سلسلة "ABC". في الواقع ، هذا هو أيضا لتوفير مساحة تخزين الذاكرة وتحسين أداء البرنامج.
4. دعنا نلقي نظرة على معلومات Bytecode لكائن السلسلة:
العام java.lang.string () ؛ واصف: () Flags: ACC_Public Code: stack = 2 ، السكان المحليون = 1 ، args_size = 1 0: aload_0 1: invokespecial #1 // method java/lang/object. 138: 4 line 139: 11 Java.Lang.String (Java.lang.String) ؛ واصف: (ljava/lang/string ؛) V أعلام: ACC_Public Code: Stack = 2 ، السكان المحليين = 2 ، args_size = 2 0: aload_0 // ادفع المتغير المحلي 0 إلى الجزء العلوي من المكدس ، مرجع إلى كائنه الخاص. 1: Invokespecial #1 // Method Java/Lang/Object. 4: aload_0 // ادفع مرجع كائنه الخاص إلى أعلى المكدس. 5: aload_1 // الضغط المرجعي للسلسلة التي تم تمريرها إلى أعلى المكدس. 6: GetField #2 // قيمة الحقل: [c // قم بإيقاف مرجع السلسلة في الجزء العلوي من المكدس وقم بتعيينه إلى متغير المثيل في #2 ، وقم بتخزينه على المكدس. 9: PUTFIELD #2 // قيمة الحقل: [C // قم بإيقاف مرجع السلسلة في الجزء العلوي من المكدس والكائن نفسه وتعيين مرجع السلسلة إلى متغير المثيل للكائن نفسه. 12: aload_0 13: aload_1 14: Getfield #3 // field hash: i 17: putfield #3 // field hash: i 20: return
من منظور Bytecode ، يمكننا أن نستنتج أن سلسلة جديدة ("ABC") تنفذ مهام مراجع السلسلة عند إنشاء كائن جديد ، بدلاً من نسخ السلاسل. ما سبق هو تحليل وملخص لتخصيص الذاكرة للسلاسل من منظور رمز المصدر ورمز البايت.
التحليل أعلاه وملخص تخصيص ذاكرة سلسلة Java (الموصى به) هو كل المحتوى الذي أشاركه معك. آمل أن تتمكن من إعطائك مرجعًا وآمل أن تتمكن من دعم wulin.com أكثر.