คุณมักจะเห็นการอภิปรายเกี่ยวกับการจัดสรรหน่วยความจำของสตริง Java เมื่อทำงานในส่วนหลักบนอินเทอร์เน็ตเช่น: String A = "123", String B = สตริงใหม่ ("123") สตริงสองรูปแบบนี้เก็บไว้ที่ไหน ในความเป็นจริงค่าตัวอักษรของสตริง "123" ในทั้งสองรูปแบบนี้ไม่ได้ถูกเก็บไว้ในสแต็กหรือบนกองที่รันไทม์ พวกเขาจะถูกเก็บไว้ในพื้นที่คงที่บางอย่างในพื้นที่วิธีการและมีเพียงหนึ่งสำเนาที่เก็บไว้ในหน่วยความจำสำหรับค่าตัวอักษรสตริงเดียวกัน เราจะวิเคราะห์ด้วยตัวอย่างด้านล่าง
1. ตัวดำเนินการ == ใช้ในสองกรณีที่เปรียบเทียบการเปรียบเทียบการอ้างอิงสตริงสองรายการ:
Public Class StringTest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {// ส่วนที่ 1 สตริง S1 = "ฉันรักจีน"; สตริง s2 = "ฉันรักจีน"; System.out.println ("ผลลัพธ์:" + s1 == s2); // ผลการรันโปรแกรมเป็นจริง // ส่วนที่ 2 สตริง S3 = สตริงใหม่ ("ฉันรักจีน"); สตริง S4 = สตริงใหม่ ("ฉันรักจีน"); System.out.println ("ผลลัพธ์:" + s3 == s4); // ผลการรันโปรแกรมเป็นเท็จ}}เรารู้ว่าตัวดำเนินการ == ใน Java เปรียบเทียบค่าของตัวแปร ค่าของตัวแปรที่สอดคล้องกับประเภทอ้างอิงจะเก็บที่อยู่ของวัตถุอ้างอิง สตริงที่นี่คือประเภทการอ้างอิงและค่าของตัวแปรทั้งสี่ที่นี่จริง ๆ แล้วเก็บที่อยู่ของสตริง ผลการดำเนินการสำหรับ Part2 นั้นชัดเจนเนื่องจากตัวดำเนินการใหม่จะทำให้ JVM สร้างวัตถุใหม่ในกองที่รันไทม์และที่อยู่ของวัตถุทั้งสองที่แตกต่างกันนั้นแตกต่างกัน อย่างไรก็ตามจากผลการดำเนินการของส่วนที่ 1 จะเห็นได้ว่า S1 และ S2 เป็นที่อยู่เดียวกันชี้ไป ดังนั้นสตริงชี้ไปที่ตัวแปร S1 และ S2 ที่เก็บไว้ที่ไหน? JVM จัดการสตริงได้อย่างไร? ในทำนองเดียวกันสำหรับวัตถุสตริงที่แตกต่างกันในกองที่ชี้ไปที่ตัวแปร S3 และ S4 พวกเขาจะบันทึกสตริง "I Love China" ในพื้นที่วัตถุของตัวเองหรือไม่? เพื่อที่จะเข้าใจว่า JVM จัดการสตริงได้อย่างไรก่อนอื่นเราจะดูคำแนะนำของไบต์ที่สร้างโดยคอมไพเลอร์ Java ผ่านคำสั่ง bytecode เราวิเคราะห์ว่าการดำเนินการใดจะดำเนินการโดย JVM
2. ข้อมูลต่อไปนี้เป็นข้อมูลบางอย่างที่สร้างโดยโปรแกรม เครื่องหมายสีแดงเป็นส่วนที่เราต้องใส่ใจ
พูลคงที่: #1 = คลาส #2 // stringtest #2 = utf8 stringtest #3 = คลาส #4 // java/lang/object #4 = utf8 java/lang/object #5 = utf8 <init> #6 = utf8 () v #7 = utf8 code #8 = methodref #3. #9 // java/lang #5: #6 // "<int>" :() V #10 = UTF8 LinenumberTable #11 = UTF8 LocalVariabletable #12 = UTF8 นี้ #13 = UTF8 LSTRINGTEST; #14 = UTF8 Main #15 = UTF8 ([LJAVA/LANG/String;) V #16 = String #17 // ฉันรักจีนอ้างอิงถึงที่อยู่สตริง #17 = UTF8 ฉันรักจีน #18 = FieldRef #19. #21 // Java/Lang/System #19 = คลาส #20 // java/lang/system #20 = UTF8 java/lang/ระบบ #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 = สตริง #27 // ผลลัพธ์: #27 = UTF8 ผลลัพธ์: #28 = MethodRef #24. #29 // Java/Lang/Lang/StringBuilder. // "<init>" :( ljava/lang/string;) v#30 = utf8 (ljava/lang/string;) v#31 = methodref#24.#32 // java/lang/stringbuilder.append: (z) ljava/lang/lang/stringbuilder;#32 = nameandty ผนวก: (z) ljava/lang/stringbuilder;#33 = utf8 ผนวก#34 = utf8 (z) ljava/lang/stringbuilder;#35 = methodref#24.#36 // java/lang/lang/stringbuilder.tostring :() toString :() ljava/lang/string;#37 = UTF8 TOSTRING#38 = UTF8 () ljava/lang/string;#39 = methodRef#40.#42 // java/io/printstream.println: (ljava/lang; java/io/printstream#42 = nameandType#43:#30 // println: (ljava/lang/string;) v#43 = utf8 println#44 = คลาส#45 // java/lang/string#45 = utf8 java/lang/lang java/lang/string. "<init>" :( ljava/lang/string;) v#47 = utf8 args#48 = utf8 [ljava/lang/string;#49 = utf8 s1#50 = utf8 ljava/lang/string;#51 = utf8 s2#52 = utf8 stackmaptable#55 = คลาส#48 // "[ljava/lang/string;"#56 = UTF8 SourceFile#57 = UTF8 StringTest.Java ............ // คำสั่ง bytecode สำหรับวิธีการที่เกี่ยวข้องถูกอธิบายและดำเนินการโดย JVM Runtime โมฆะคงที่สาธารณะหลัก (java.lang.string []); descriptor: ([ljava/lang/string;) V Flags: acc_public, acc_static รหัส: stack = 4, locals = 5, args_size = 1 0: ldc #16 // สตริงฉันรักจีนคำสั่งนี้หมายถึงสัญลักษณ์ที่ #16 ของสระว่ายน้ำคงที่ คำสั่งนี้สอดคล้องกับคำสั่งต่อไปนี้ 2. สตริง S1 = "ฉันรักจีน" คำสั่ง 2 ในโปรแกรม: store_1 // กำหนดการอ้างอิงวัตถุที่ด้านบนของสแต็กไปยังตัวแปรท้องถิ่น 1. 3: LDC #16 // สตริงฉันรักจีนคำสั่งที่ 0 คะแนนเท่ากัน คำสั่งนี้และคำสั่งต่อไปนี้ 5 สอดคล้องกับคำสั่งสตริง S2 = "ฉันรักจีน" ในโปรแกรม 5: store_2 // กำหนดการอ้างอิงวัตถุให้กับตัวแปรท้องถิ่นที่ด้านบนของสแต็ก 2. 6: getstatic #18 // ฟิลด์ Java/lang/system.out: ljava/io/printstream; 9: ใหม่ #24 // คลาส java/lang/stringbuilder 12: dup 13: ldc #26 // สตริงผลลัพธ์: 15: invokespecial #28 // วิธี java/lang/stringbuilder. "<int>" :( ljava/lang/string; เปรียบเทียบว่าพวกเขาเท่ากันหรือไม่ให้ไปที่คำสั่ง 27 ดำเนินการดำเนินการคำสั่งถัดไปอย่างเท่าเทียมกัน 23: iconst_1 24: goto 28 27: iconst_0 28: invokevirtual #31 // เมธอด 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 // คลาส java/lang/สตริงสร้างวัตถุซึ่งอยู่ที่การอ้างอิงของสระว่ายน้ำคงที่ #44 นี่คือวัตถุสตริง 40: DUP // คัดลอกสำเนาของวัตถุที่ด้านบนของสแต็กแล้วดันไปที่ด้านบนของสแต็ก 41: LDC #16 // String I Love China เหมือนกับ 0, 3 คำแนะนำ 43: Invokespecial #46 // วิธี java/lang/string. "<init>" :( ljava/lang/string;) v 46: store_3 47: ใหม่ #44 // คลาส java/lang/string // string i love string. 53: Invokespecial #46 // เมธอด java/lang/string. "<init>" :( ljava/lang/string;) v, เรียกใช้วิธีการเริ่มต้นเริ่มต้นของวัตถุตามการอ้างอิงวัตถุที่เกี่ยวข้องที่ด้านบนของสแต็กและการอ้างอิงสตริง java/lang/system.out: ljava/io/printstream; 61: ใหม่ #24 // คลาส java/lang/stringbuilder 64: dup 65: ldc #26 // สตริงผลลัพธ์: 67: invokespecial #28 // วิธี java/lang/stringbuilder.append: (z) ljava/lang/lang/stringbuilder; 84: invokevirtual #35 // วิธี java/lang/stringbuilder.toString :() ljava/lang/stringbuilder.append: (z) ljava/lang/stringbuilder; 84: invokevirtual #35 // วิธี java/lang/stringbuilder.toString :() ljava/lang/stringbuilder; 87: invokevirtual #39 // วิธี java/io/printstream.println: (ljava/lang/string;) v 90: return ......... linenumbertable: บรรทัด 7: 0line 8: 3line 9: 6line 11: 37Line 12: 47Line 13: 58Line 14: 90Localvariabletable [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 เกี่ยวข้องกับการสนทนาของเรา ผ่านทางไบต์ที่สร้างขึ้นเราสามารถวาดข้อสรุปต่อไปนี้สำหรับโปรแกรมตัวอย่าง
1). เมื่อ Java Compiler รวบรวมโปรแกรมลงใน bytecode ค่าคงที่สตริง "I Love China" พบครั้งแรกจะกำหนดว่ามีอยู่ในสระคงที่ bytecode หรือไม่ หากไม่มีอยู่มันจะไม่สร้างมันขึ้นมา นั่นคือสตริงที่เท่าเทียมกันสามารถเก็บสำเนาได้เพียงครั้งเดียว มันสามารถพบได้ผ่านการอ้างอิงเชิงสัญลักษณ์เพื่อให้ตัวแปรสตริง S1 และ S2 ในโปรแกรมชี้ไปที่ค่าคงที่สตริงเดียวกันในพูลคงที่ ที่รันไทม์ JVM จะเก็บสตริงค่าคงที่ในพูลคงที่ไบต์ในตำแหน่งของพื้นที่วิธีการทั่วไปที่เรียกว่าพูลคงที่และสตริงจะเข้าถึงผ่านดัชนีในรูปแบบของอาร์เรย์อักขระ JVM ชี้ที่อยู่อ้างอิงสัมพัทธ์ของสตริงที่ชี้ไปที่ S1 และ S2 ไปยังที่อยู่หน่วยความจำจริงของสตริงที่รันไทม์
2). สำหรับสตริง S3 = สตริงใหม่ ("ฉันรักจีน"), สตริง S4 = สตริงใหม่ ("ฉันรักจีน") จาก bytecode จะเห็นได้ว่ามันเรียกคำสั่งใหม่ JVM จะสร้างวัตถุที่แตกต่างกันสองรายการที่รันไทม์และ S3 และ S4 ชี้ไปที่ที่อยู่วัตถุที่แตกต่างกัน ดังนั้นผลลัพธ์ของการเปรียบเทียบ S3 == S4 จึงเป็นเท็จ
ประการที่สองสำหรับการเริ่มต้นของวัตถุ S3 และ S4 จะเห็นได้จาก bytecode ว่าวิธีการเริ่มต้นของวัตถุถูกเรียกและการอ้างอิง "ฉันรักจีน" ในสระคงที่ ดังนั้นการสร้างวัตถุสตริงและการเริ่มต้นคืออะไร? เราสามารถตรวจสอบซอร์สโค้ดของสตริงและ bytecode ที่สร้างโดยวัตถุสตริงเพื่อให้เข้าใจได้ดีขึ้นว่าสตริงถูกคัดลอกภายในวัตถุหรืออ้างอิงโดยตรงไปยังที่อยู่ของพูลคงที่ที่สอดคล้องกับสตริงถูกชี้ไปที่
3. ส่วนหนึ่งของซอร์สโค้ดของวัตถุสตริง:
<span style = "ตัวอักษรขนาด: 14pt"> สตริงคลาสสุดท้ายของสาธารณะใช้ java.io.serializable, เปรียบเทียบ <string>, charsequence { /** ค่าที่ใช้สำหรับการจัดเก็บอักขระ */ ค่าถ่านสุดท้ายส่วนตัว []; / ** แคชรหัสแฮชสำหรับสตริง*/ ส่วนตัว int แฮช; // ค่าเริ่มต้นเป็น 0 สตริงสาธารณะ () {this.value = ถ่านใหม่ [0]; } </span> <span style = "พื้นหลังสี: #FFFFFF; font-size: 18pt"> สตริงสาธารณะ (สตริงต้นฉบับ) {this.value = Original.value; this.hash = Original.hash; } </span>จากซอร์สโค้ดเราจะเห็นว่ามีค่าตัวแปรอินสแตนซ์ถ่าน [] ในคลาสสตริง ด้วยวิธีการก่อสร้างเราจะเห็นว่าวัตถุไม่ได้ทำการคัดลอกการดำเนินการเมื่อเริ่มต้น แต่กำหนดเฉพาะการอ้างอิงที่อยู่ของวัตถุสตริงที่ผ่านไปยังค่าตัวแปรอินสแตนซ์ จากนี้เราสามารถสรุปได้ว่าแม้ในขณะที่วัตถุสตริงถูกสร้างขึ้นโดยใช้สตริงใหม่ ("ABC") พื้นที่จะถูกจัดสรรสำหรับวัตถุในหน่วยหน่วยความจำ แต่ไม่มีข้อมูลเกี่ยวกับ "ABC" เองถูกเก็บไว้ในกอง แต่การอ้างอิงถึงสตริง "ABC" จะเริ่มต้นภายในตัวแปรอินสแตนซ์ของสตริง "ABC" ในความเป็นจริงนี่คือการประหยัดพื้นที่จัดเก็บหน่วยความจำและปรับปรุงประสิทธิภาพของโปรแกรม
4. ลองดูข้อมูลไบต์ของวัตถุสตริง:
สาธารณะ java.lang.string (); Descriptor: () V Flags: ACC_Public Code: stack = 2, locals = 1, args_size = 1 0: aload_0 1: invokespecial #1 // เมธอด java/lang/object. "<int>" :() v 4: aload_0 5: iconst_0 6: newarray 138: 4 บรรทัด 139: 11 Java.lang.string (java.lang.string); Descriptor: (ljava/lang/string;) V Flags: ACC_Public รหัส: stack = 2, locals = 2, args_size = 2 0: aload_0 // ผลักดันตัวแปรท้องถิ่น 0 ไปด้านบนของสแต็กอ้างอิงถึงวัตถุของตัวเอง 1: Invokespecial #1 // เมธอด java/lang/object. "<init>" :() v ปรากฏขึ้นบนวัตถุสแต็กด้านบนเพื่ออ้างถึงวิธีการเริ่มต้นที่ #1 ของวัตถุ 4: aload_0 // ผลักดันการอ้างอิงของวัตถุของตัวเองไปที่ด้านบนของสแต็ก 5: aload_1 // การส่งการอ้างอิงสตริงที่ผ่านไปที่ด้านบนของสแต็ก 6: GetField #2 // ค่าฟิลด์: [C // ป๊อปอัพการอ้างอิงสตริงที่ด้านบนของสแต็กและกำหนดให้กับตัวแปรอินสแตนซ์ที่ #2 และเก็บไว้ในสแต็ก 9: Putfield #2 // ค่าฟิลด์: [C // ป๊อปอัพการอ้างอิงสตริงที่ด้านบนของสแต็กและวัตถุเองและกำหนดการอ้างอิงสตริงให้กับตัวแปรอินสแตนซ์ของวัตถุเอง 12: aload_0 13: aload_1 14: getfield #3 // ฟิลด์แฮช: ฉัน 17: พัตฟิลด์ #3 // ฟิลด์แฮช: ฉัน 20: กลับมา
จากมุมมองของ bytecode เราสามารถสรุปได้ว่าสตริงใหม่ ("ABC") ดำเนินการกำหนดของการอ้างอิงสตริงเมื่อสร้างวัตถุใหม่แทนที่จะคัดลอกสตริง ข้างต้นคือการวิเคราะห์และสรุปการจัดสรรหน่วยความจำของสตริงจากมุมมองของซอร์สโค้ดและรหัสไบต์
การวิเคราะห์ข้างต้นและบทสรุปของการจัดสรรหน่วยความจำสตริง Java (แนะนำ) เป็นเนื้อหาทั้งหมดที่ฉันแบ่งปันกับคุณ ฉันหวังว่าคุณจะให้ข้อมูลอ้างอิงและฉันหวังว่าคุณจะสนับสนุน wulin.com มากขึ้น