Java Constant Pool เป็นหัวข้อที่ยาวนานและยังเป็นที่ชื่นชอบของผู้สัมภาษณ์ มีคำถามมากมาย ฉันจะสรุปในครั้งนี้
ทฤษฎี
ก่อนอื่นให้แสดงการกระจายหน่วยความจำเสมือน JVM:
ตัวนับโปรแกรม เป็นไปป์ไลน์สำหรับ JVM ในการดำเนินการโปรแกรมจัดเก็บคำแนะนำการกระโดด มันลึกซึ้งเกินไปและฉันไม่เข้าใจ
สแต็กวิธีการในท้องถิ่น คือสแต็กที่ใช้โดย JVM เพื่อเรียกวิธีการระบบปฏิบัติการ
สแต็กเครื่องเสมือน เป็นสแต็กที่ใช้โดย JVM เพื่อเรียกใช้รหัส Java
พื้นที่วิธี การเก็บค่าคงที่บางตัวตัวแปรคงที่ข้อมูลชั้นเรียน ฯลฯ ซึ่งสามารถเข้าใจได้ว่าเป็นตำแหน่งที่เก็บข้อมูลของไฟล์คลาสในหน่วยความจำ
ฮีปเครื่องเสมือน เป็นกองที่ใช้โดย JVM เพื่อเรียกใช้รหัส Java
สระว่ายน้ำคงที่ใน Java นั้นแบ่งออกเป็นสองรูปแบบ: สระคงที่คงที่ และ สระว่ายน้ำคงที่รันไทม์
พูลคงที่คงที่ ที่เรียกว่าเป็นพูลคงที่ในไฟล์ *.class พูลคงที่ในไฟล์คลาสไม่เพียง แต่มีตัวอักษรสตริง (หมายเลข) แต่ยังมีข้อมูลเกี่ยวกับคลาสและวิธีการครอบครองพื้นที่ส่วนใหญ่ของไฟล์คลาส
พูลคงที่รันไทม์ เป็นเครื่องเสมือน JVM จะโหลดพูลคงที่ในไฟล์คลาสลงในหน่วยความจำหลังจากเสร็จสิ้นการดำเนินการโหลดคลาสและบันทึกไว้ใน พื้นที่เมธอด สระว่ายน้ำคงที่ที่เรามักจะเรียกว่าหมายถึงสระว่ายน้ำคงที่รันไทม์ในพื้นที่วิธีการ
ต่อไปเราอ้างตัวอย่างบางส่วนของพูลคงที่ที่ได้รับความนิยมบนอินเทอร์เน็ตแล้วอธิบายพวกเขา
สตริง s1 = "สวัสดี"; สตริง s2 = "สวัสดี"; สตริง s3 = "hel" + "lo"; สตริง s4 = "hel" + สตริงใหม่ ("lo"); สตริง s5 = สตริงใหม่ ("สวัสดี"); สตริง s6 = s5.intern (); สตริง s7 = "h"; สตริง s8 = "ello"; String S9 = S7 + S8; System.out.println (S1 == S2); // truesystem.out.println (S1 == S3); // truesystem.out.println (S1 == S4); // falsesystem.out.println (S1 == S9); // falsesystem.out.println (S4 == S5); // falsesystem.out.println (S1 == S6); // จริงก่อนอื่นทั้งหมดใน Java ตัวดำเนินการ == ใช้โดยตรงและที่อยู่อ้างอิงของสองสตริงจะถูกเปรียบเทียบไม่ใช่เนื้อหา โปรดใช้ String.equals () เพื่อเปรียบเทียบเนื้อหา
S1 == S2 เข้าใจง่ายมาก เมื่อได้รับมอบหมาย S1 และ S2 พวกเขาจะใช้ตัวอักษรสตริง เพื่อให้มันตรงไปตรงมาพวกเขาเขียนสายตรงถึงตาย ในระหว่างการรวบรวมตัวอักษรนี้จะถูกวางลงในพูลคงที่ของไฟล์คลาสโดยตรงดังนั้นจึงตระหนักถึงการใช้ซ้ำ หลังจากโหลดพูลคงที่ที่รันไทม์แล้ว S1 และ S2 จะชี้ไปที่ที่อยู่หน่วยความจำเดียวกันดังนั้นจึงเท่ากัน
มีหลุมใน S1 == S3 แม้ว่า S3 จะเป็นสตริงที่แยกออกจากกันอย่างไดนามิกชิ้นส่วนทั้งหมดที่เกี่ยวข้องในการประกบนั้นเป็นที่รู้จักกันในตัวอักษร ในช่วงระยะเวลาการรวบรวมการประกบนี้จะได้รับการปรับให้เหมาะสมและคอมไพเลอร์จะช่วยให้คุณแตกหักโดยตรง ดังนั้นสตริง s3 = "hel" + "lo"; ได้รับการปรับให้เหมาะสมกับสตริง s3 = "สวัสดี"; ในไฟล์คลาสดังนั้น S1 == S3 จึงเป็นจริง
S1 == S4 แน่นอนไม่เท่ากัน แม้ว่า S4 จะถูกประกบกันส่วนสตริงใหม่ ("LO") ไม่ได้เป็นตัวอักษรที่รู้จัก แต่เป็นส่วนที่คาดเดาไม่ได้ คอมไพเลอร์จะไม่ปรับให้เหมาะสม คุณต้องรอจนกว่าจะเรียกใช้เพื่อกำหนดผลลัพธ์ เมื่อรวมกับทฤษฎีบท ค่าคงที่ของสตริง คุณจะรู้ว่ามีการจัดสรร S4 ที่ไหนดังนั้นที่อยู่จะต้องแตกต่างกัน ภาพสั้น ๆ เพื่อชี้แจงแนวคิด:
S1 == S9 ไม่เท่ากันและเหตุผลก็คล้ายกัน แม้ว่าตัวอักษรสตริงที่ใช้โดย S7 และ S8 เมื่อกำหนดค่าเมื่อประกบกันใน S9, S7 และ S8 นั้นไม่สามารถคาดเดาได้ ท้ายที่สุดคอมไพเลอร์เป็นคอมไพเลอร์และไม่สามารถใช้เป็นล่ามได้ดังนั้นจึงไม่ได้รับการปรับให้เหมาะสม เมื่อมีการรันสตริงใหม่ที่เชื่อมต่อกับ S7 และ S8 ไม่แน่ใจในกองและไม่สามารถเหมือนกับที่อยู่ S1 ในสระว่ายน้ำคงที่ของพื้นที่วิธีการ
S4 == S5 ไม่จำเป็นต้องอธิบายอีกต่อไปมันไม่เท่ากันแน่นอนทั้งคู่อยู่ในกอง แต่ที่อยู่นั้นแตกต่างกัน
ความเท่าเทียมกันของ S1 == S6 นั้นมาจากวิธีการฝึกงานอย่างสมบูรณ์ S5 อยู่ในกองและเนื้อหาเป็นสวัสดี วิธีการฝึกงานจะพยายามเพิ่มสตริง Hello ลงในพูลคงที่และส่งคืนที่อยู่ในพูลคงที่ เนื่องจากมีสตริงสวัสดีในพูลคงที่วิธีการฝึกงานส่งคืนที่อยู่โดยตรง ในขณะที่ S1 ชี้ไปที่สระคงที่ในช่วงระยะเวลาการรวบรวมดังนั้น S1 และ S6 จึงชี้ไปที่ที่อยู่เดียวกันซึ่งเท่ากัน
ณ จุดนี้เราสามารถวาดข้อสรุปที่สำคัญมากสามประการ:
คุณต้องให้ความสนใจกับพฤติกรรมในช่วงระยะเวลาการรวบรวมเพื่อให้เข้าใจสระคงที่ดีขึ้น
ค่าคงที่ในพูลคงที่รันไทม์โดยทั่วไปมาจากพูลคงที่ในแต่ละไฟล์คลาส
เมื่อโปรแกรมกำลังทำงาน JVM จะไม่เพิ่มค่าคงที่ลงในกลุ่มคงที่โดยอัตโนมัติเว้นแต่จะเพิ่มค่าคงที่ลงในสระคงที่ด้วยตนเอง (เช่นการเรียกใช้วิธีการฝึกงาน)
ข้างต้นเกี่ยวข้องกับพูลคงที่สตริงเท่านั้น ในความเป็นจริงมีสระว่ายน้ำคงที่จำนวนเต็มพูลคงที่จุดลอยตัว ฯลฯ แต่พวกเขามีความคล้ายคลึงกัน แต่ไม่สามารถเพิ่มจำนวนเงินเชิงตัวเลขได้ด้วยตนเอง ค่าคงที่ในกลุ่มคงที่จะถูกกำหนดเมื่อโปรแกรมเริ่มต้น ตัวอย่างเช่นช่วงคงที่ในพูลคงที่จำนวนเต็มคือ: -128 ~ 127 ตัวเลขในช่วงนี้เท่านั้นที่สามารถใช้สำหรับพูลคงที่
ฝึกฝน
ต้องพูดทฤษฎีมากมายให้สัมผัสกับสระคงที่จริง
ดังที่ได้กล่าวไว้ก่อนหน้านี้มีพูลคงที่คงที่ในไฟล์คลาส สระว่ายน้ำคงที่นี้ถูกสร้างขึ้นโดยคอมไพเลอร์และใช้ในการจัดเก็บตัวอักษรในไฟล์ต้นฉบับ Java (บทความนี้มุ่งเน้นไปที่ตัวอักษรเท่านั้น) สมมติว่าเรามีรหัส Java ต่อไปนี้:
สตริง s = "สวัสดี";
เพื่อความสะดวกมันง่ายขนาดนั้นถูกต้อง! หลังจากรวบรวมรหัสลงในไฟล์คลาสแล้วให้ใช้ WinHex เพื่อเปิดไฟล์คลาส Binary Format ดังที่แสดงในภาพ:
มาอธิบายโครงสร้างของไฟล์คลาสสั้น ๆ 4 ไบต์ที่จุดเริ่มต้นคือหมายเลขวิเศษของไฟล์คลาสซึ่งใช้เพื่อระบุสิ่งนี้เป็นไฟล์คลาส เพื่อให้มันตรงไปตรงมามันเป็นส่วนหัวไฟล์ซึ่งก็คือ: ca fe ba be
4 ไบต์ถัดไปคือหมายเลขเวอร์ชันของ Java และหมายเลขเวอร์ชันที่นี่คือ 34 เนื่องจากผู้เขียนถูกรวบรวมด้วย JDK8 และหมายเลขเวอร์ชันสอดคล้องกับระดับของรุ่น JDK เวอร์ชันที่สูงขึ้นสามารถเข้ากันได้กับเวอร์ชันที่ต่ำกว่า แต่รุ่นที่ต่ำกว่าไม่สามารถเรียกใช้เวอร์ชันที่สูงกว่าได้ ดังนั้นหากผู้อ่านวันหนึ่งต้องการทราบว่าไฟล์คลาสของ JDK รุ่นอื่น ๆ ถูกรวบรวมด้วยคุณสามารถดู 4 ไบต์เหล่านี้ได้
ถัดไปคือทางเข้าสระว่ายน้ำคงที่ จำนวนค่าคงที่พูลคงที่จะถูกระบุโดย 2 ไบต์ที่ทางเข้า ในตัวอย่างนี้ค่าคือ 00 1a มันถูกแปลเป็นทศนิยมและ 26 ซึ่งหมายความว่ามีค่าคงที่ 25 ค่า ค่าคงที่ที่ 0 เป็นค่าพิเศษดังนั้นจึงมีค่าคงที่เพียง 25 ค่าเท่านั้น
สระว่ายน้ำคงที่เก็บค่าคงที่ประเภทต่างๆ พวกเขาทั้งหมดมีประเภทของตัวเองและข้อกำหนดการจัดเก็บของตัวเอง บทความนี้มุ่งเน้นเฉพาะค่าคงที่สตริง สตริงค่าคงที่เริ่มต้นด้วย 01 (1 ไบต์) จากนั้นบันทึกความยาวสตริงด้วย 2 ไบต์จากนั้นเนื้อหาจริงของสตริง ในกรณีนี้คือ: 01 00 02 68 69
ถัดไปพูดคุยเกี่ยวกับสระว่ายน้ำคงที่รันไทม์ เนื่องจากพูลคงที่รันไทม์อยู่ในพื้นที่เมธอดเราสามารถตั้งค่าขนาดพื้นที่วิธีผ่านพารามิเตอร์ JVM: -xx: Permsize, -xx: Maxpermsize ดังนั้นจึง จำกัด ขนาดพูลคงที่ทางอ้อม
สมมติว่าพารามิเตอร์การเริ่มต้น JVM คือ: -xx: permsize = 2m -xx: maxpermsize = 2m จากนั้นเรียกใช้รหัสต่อไปนี้:
// เก็บข้อมูลอ้างอิงเพื่อป้องกันรายการรวบรวมขยะอัตโนมัติ <String> list = new ArrayList <String> (); int i = 0; ในขณะที่ (จริง) {// เพิ่มรายการคงที่ list.add (string.valueof (i ++).โปรแกรมจะโยนทันที: ข้อยกเว้นในเธรด "Main" Java.lang.outofMemoryError: ข้อยกเว้นพื้นที่ Permgen พื้นที่ Permgen เป็นพื้นที่วิธีการซึ่งเพียงพอที่จะระบุว่าสระว่ายน้ำคงที่อยู่ในพื้นที่วิธีการ
ใน JDK8 พื้นที่วิธีการถูกลบออกและพื้นที่ Metaspace ถูกแทนที่ ดังนั้นเราจำเป็นต้องใช้พารามิเตอร์ JVM ใหม่: -xx: MaxMetaspacesize = 2m และยังคงเรียกใช้รหัสด้านบนการขว้าง: java.lang.outofmemoryError: ข้อยกเว้น Metaspace ในทำนองเดียวกันมีการอธิบายว่าสระว่ายน้ำคงที่รันไทม์จะถูกแบ่งออกเป็นพื้นที่ Metaspace สำหรับความรู้เฉพาะเกี่ยวกับพื้นที่ Metaspace โปรดค้นหาด้วยตัวคุณเอง
รหัสทั้งหมดในบทความนี้ได้รับการทดสอบและผ่านภายใต้ JDK7 และ JDK8 JDK รุ่นอื่นอาจมีความแตกต่างเล็กน้อย โปรดสำรวจด้วยตัวคุณเอง