1. คำนำ
Java เป็นภาษาการเขียนโปรแกรมระดับสูงที่มุ่งเน้นไปที่แพลตฟอร์ม โปรแกรม Java ทำงานบน Java Virtual Machines (JVMS) และจัดการหน่วยความจำโดย JVMS นี่คือความแตกต่างที่ใหญ่ที่สุดจาก C ++ แม้ว่าหน่วยความจำได้รับการจัดการโดย JVM แต่เราต้องเข้าใจว่า JVM จัดการหน่วยความจำได้อย่างไร ไม่เพียง แต่ JVM หนึ่งเดียวและอาจมีเครื่องเสมือนจริงหลายสิบเครื่อง แต่การออกแบบเครื่องเสมือนที่สอดคล้องกับข้อกำหนดจะต้องเป็นไปตาม "ข้อกำหนดของเครื่องเสมือน Java" บทความนี้ขึ้นอยู่กับคำอธิบายของเครื่องเสมือนฮอตสปอตและจะถูกกล่าวถึงหากมีความแตกต่างกับเครื่องเสมือนอื่น ๆ บทความนี้ส่วนใหญ่อธิบายถึงวิธีการกระจายหน่วยความจำใน JVM วิธีการจัดเก็บและเข้าถึงโปรแกรมวัตถุของ Java และข้อยกเว้นที่เป็นไปได้ในพื้นที่หน่วยความจำต่างๆ
2. การกระจายหน่วยความจำ (ภูมิภาค) ใน JVM
เมื่อดำเนินการโปรแกรม Java JVM จะแบ่งหน่วยความจำออกเป็นหลาย ๆ ด้านข้อมูลที่แตกต่างกันสำหรับการจัดการ พื้นที่เหล่านี้มีฟังก์ชั่นการสร้างและการทำลายที่แตกต่างกัน บางพื้นที่ได้รับการจัดสรรเมื่อกระบวนการ JVM เริ่มต้นขึ้นในขณะที่บางพื้นที่เกี่ยวข้องกับวงจรชีวิตของเธรดผู้ใช้ (เธรดของโปรแกรมเอง) ตามข้อกำหนดของ JVM พื้นที่หน่วยความจำที่จัดการโดย JVM จะถูกแบ่งออกเป็นพื้นที่ข้อมูลรันไทม์ต่อไปนี้:
1. สแต็กเครื่องเสมือน
พื้นที่หน่วยความจำนี้เป็นส่วนตัวโดยเธรดและถูกสร้างขึ้นเมื่อเธรดเริ่มต้นและถูกทำลายเมื่อถูกทำลาย โมเดลหน่วยความจำสำหรับการดำเนินการของวิธี Java ที่อธิบายโดยสแต็กเครื่องเสมือน: แต่ละวิธีจะสร้างเฟรมสแต็ก (เฟรมสแต็ก) ที่จุดเริ่มต้นของการดำเนินการซึ่งใช้ในการจัดเก็บตารางตัวแปรท้องถิ่นตัวถูกดำเนินการสแต็กลิงก์แบบไดนามิกทางออก ฯลฯ การดำเนินการและผลตอบแทนของแต่ละวิธี
ตามชื่อที่แนะนำตารางตัวแปรท้องถิ่นเป็นพื้นที่หน่วยความจำที่เก็บตัวแปรท้องถิ่น: จัดเก็บชนิดข้อมูลพื้นฐาน (8 ประเภทข้อมูลพื้นฐาน Java) ประเภทอ้างอิงและที่อยู่ส่งคืนที่สามารถพบได้ในช่วงระยะเวลาคอมไพเลอร์; ประเภทยาวและสองเท่าที่ครอบครอง 64 บิตจะครอบครองพื้นที่ตัวแปรท้องถิ่น 2 แห่งและประเภทข้อมูลอื่น ๆ เท่านั้นที่ครอบครอง 1 เท่านั้น เนื่องจากขนาดของประเภทถูกกำหนดและจำนวนของตัวแปรสามารถทราบได้ในช่วงระยะเวลาการรวบรวมตารางตัวแปรท้องถิ่นมีขนาดที่รู้จักเมื่อมันถูกสร้างขึ้น ส่วนหนึ่งของพื้นที่หน่วยความจำสามารถจัดสรรได้ในช่วงระยะเวลาการรวบรวมและไม่จำเป็นต้องปรับเปลี่ยนขนาดตารางตัวแปรท้องถิ่นในระหว่างการรันวิธีการ
ในข้อกำหนดของเครื่องเสมือนมีการระบุสองข้อยกเว้นสำหรับพื้นที่หน่วยความจำนี้:
1. หากความลึกของสแต็คที่ร้องขอโดยเธรดนั้นสูงกว่าความลึกที่อนุญาต (?) จะมีการโยนข้อยกเว้น StackOverflowError
2. หากเครื่องเสมือนสามารถขยายแบบไดนามิกได้เมื่อการขยายตัวไม่สามารถใช้กับหน่วยความจำที่เพียงพอจะมีการโยนข้อยกเว้น OutOfMemory
2. วิธีการในท้องถิ่นสแต็ก
สแต็กวิธีการในท้องถิ่นยังเป็นเธรดส่วนตัวและฟังก์ชั่นของมันเกือบจะเหมือนกับสแต็กเครื่องเสมือน: สแต็กเครื่องเสมือนให้บริการสแต็กแบบอิน-ออกสำหรับการดำเนินการ Java Method ในขณะที่ Method Method Local ให้บริการสำหรับเครื่องเสมือนเพื่อดำเนินการวิธีการดั้งเดิม
ในข้อกำหนดของเครื่องเสมือนไม่มีระเบียบข้อบังคับเกี่ยวกับวิธีการใช้งานของสแต็กวิธีการในท้องถิ่นและสามารถนำไปใช้งานได้อย่างอิสระโดยเครื่องเสมือนที่เฉพาะเจาะจง เครื่องเสมือนฮอตสปอตรวมสแต็กเครื่องเสมือนจริงและสแต็กวิธีการในท้องถิ่นเป็นหนึ่งเดียว สำหรับเครื่องเสมือนอื่น ๆ ที่จะใช้วิธีนี้ผู้อ่านสามารถสอบถามข้อมูลที่เกี่ยวข้องหากพวกเขาสนใจ
เช่นเดียวกับสแต็คเครื่องเสมือนจริงสแต็กวิธีการในท้องถิ่นจะโยน StackOverflowError和OutOfMemory
3. โปรแกรมเครื่องคิดเลข
เครื่องคิดเลขโปรแกรมยังเป็นพื้นที่หน่วยความจำส่วนตัวของเธรด มันถือได้ว่าเป็นตัวบ่งชี้หมายเลขบรรทัด (ชี้ไปที่คำสั่ง) สำหรับเธรดเพื่อเรียกใช้งาน bytecode เมื่อดำเนินการ Java จะได้รับคำสั่งถัดไปที่จะดำเนินการโดยการเปลี่ยนค่าของตัวนับ คำสั่งการดำเนินการของสาขาลูปกระโดดการจัดการข้อยกเว้นการกู้คืนเธรด ฯลฯ ทั้งหมดขึ้นอยู่กับเคาน์เตอร์นี้เพื่อให้เสร็จสมบูรณ์ มัลติเธรดของเครื่องเสมือนสามารถทำได้โดยการสลับในการเปลี่ยนและจัดสรรเวลาดำเนินการของโปรเซสเซอร์ โปรเซสเซอร์ (หลักสำหรับโปรเซสเซอร์แบบมัลติคอร์) สามารถดำเนินการคำสั่งหนึ่งคำสั่งได้ทีละคำสั่งเท่านั้น ดังนั้นหลังจากเธรดดำเนินการสลับมันจะต้องกู้คืนไปยังตำแหน่งการดำเนินการที่ถูกต้อง แต่ละเธรดมีเครื่องคิดเลขโปรแกรมอิสระ
เมื่อดำเนินการวิธี Java เครื่องคิดเลขโปรแกรมจะบันทึก (ชี้ไปที่) ที่อยู่ของคำสั่ง bytecode ที่เธรดปัจจุบันกำลังดำเนินการ หากวิธีการดั้งเดิมกำลังดำเนินการค่าของเครื่องคิดเลขนี้จะไม่ได้กำหนด นี่เป็นเพราะโมเดลเธรดเครื่องเสมือนฮอตสปอตเป็นรุ่นเธรดดั้งเดิมนั่นคือแต่ละเธรด Java แมปโดยตรงเธรดของระบบปฏิบัติการ (ระบบปฏิบัติการ) เมื่อดำเนินการวิธีการดั้งเดิมมันจะถูกดำเนินการโดยตรงโดยระบบปฏิบัติการ ค่าของเคาน์เตอร์นี้ของเครื่องเสมือนนี้ไม่มีประโยชน์ เนื่องจากเครื่องคิดเลขนี้เป็นพื้นที่หน่วยความจำที่มีพื้นที่ขนาดเล็กมากส่วนตัวและไม่จำเป็นต้องขยาย เป็นพื้นที่เดียวในข้อกำหนดของเครื่องเสมือนที่ไม่ได้ระบุข้อยกเว้น OutOfMemoryError ใด ๆ
4. หน่วยความจำฮีป (กอง)
กอง Java เป็นพื้นที่หน่วยความจำที่ใช้ร่วมกันโดยเธรด อาจกล่าวได้ว่าเป็นพื้นที่หน่วยความจำที่ใหญ่ที่สุดที่จัดการโดยเครื่องเสมือนและถูกสร้างขึ้นเมื่อเครื่องเสมือนเริ่มต้น หน่วยความจำ java heap ส่วนใหญ่จะเก็บอินสแตนซ์วัตถุและอินสแตนซ์วัตถุเกือบทั้งหมด (รวมถึงอาร์เรย์) จะถูกเก็บไว้ที่นี่ ดังนั้นนี่จึงเป็นพื้นที่หน่วยความจำหลักของการรวบรวมขยะ (GC) เนื้อหาเกี่ยวกับ GC จะไม่ได้รับการอธิบายที่นี่
ตามข้อกำหนดของเครื่องเสมือนเครื่องหน่วยความจำ Java Heap สามารถอยู่ในหน่วยความจำทางกายภาพที่ไม่ต่อเนื่อง ตราบใดที่มันมีเหตุผลอย่างต่อเนื่องและไม่มีการ จำกัด การขยายพื้นที่ก็อาจเป็นขนาดคงที่หรือต้นไม้ขยาย หากหน่วยความจำฮีปไม่มีพื้นที่เพียงพอที่จะทำให้การจัดสรรอินสแตนซ์เสร็จสมบูรณ์และไม่สามารถขยายได้ข้อยกเว้น OutOfMemoryError จะถูกโยนลงไป
5. พื้นที่วิธีการ
พื้นที่วิธีการคือพื้นที่หน่วยความจำที่ใช้ร่วมกันโดยเธรดเช่นเดียวกับหน่วยความจำฮีปมันเก็บข้อมูลประเภทค่าคงที่ตัวแปรคงที่รหัสที่รวบรวมในช่วงระยะเวลาการรวบรวมทันทีและข้อมูลอื่น ๆ ข้อกำหนดของเครื่องเสมือนไม่มีข้อ จำกัด มากเกินไปในการใช้งานพื้นที่วิธีการและเช่นเดียวกับหน่วยความจำฮีปไม่จำเป็นต้องใช้พื้นที่หน่วยความจำทางกายภาพอย่างต่อเนื่องขนาดสามารถแก้ไขได้หรือปรับขนาดได้และยังสามารถเลือกไม่ให้ใช้การรวบรวมขยะ เมื่อพื้นที่วิธีการไม่สามารถปฏิบัติตามข้อกำหนดการจัดสรรหน่วยความจำได้ข้อยกเว้น OutOfMemoryError จะถูกโยนลงไป
6. หน่วยความจำโดยตรง
หน่วยความจำโดยตรงไม่ได้เป็นส่วนหนึ่งของหน่วยความจำที่ได้รับการจัดการของเครื่องเสมือน แต่ส่วนหนึ่งของหน่วยความจำนี้อาจยังคงถูกใช้บ่อย เมื่อโปรแกรม Java ใช้วิธีการดั้งเดิม (เช่น NIO, NIO, ไม่มีคำอธิบายที่นี่) หน่วยความจำอาจได้รับการจัดสรรโดยตรงนอกกอง แต่พื้นที่หน่วยความจำทั้งหมดมี จำกัด และจะมีหน่วยความจำไม่เพียงพอและข้อยกเว้น OutOfMemoryError
2. การเข้าถึงการจัดเก็บอ็อบเจ็กต์อินสแตนซ์
จุดแรกด้านบนมีคำอธิบายทั่วไปของหน่วยความจำในแต่ละพื้นที่ของเครื่องเสมือน สำหรับแต่ละพื้นที่มีปัญหาเกี่ยวกับวิธีการสร้างข้อมูลวางและเข้าถึง มาใช้หน่วยความจำฮีปที่ใช้กันมากที่สุดเป็นตัวอย่างเพื่อพูดคุยเกี่ยวกับทั้งสามด้านนี้ตามฮอตสปอต
1. การสร้างวัตถุอินสแตนซ์
เมื่อเครื่องเสมือนจริงดำเนินการคำสั่งใหม่ก่อนอื่นจะค้นหาการอ้างอิงสัญลักษณ์คลาสของวัตถุการสร้างจากพูลคงที่และตัดสินว่าคลาสได้รับการโหลดและเริ่มต้นหรือไม่ หากไม่ได้โหลดกระบวนการเริ่มต้นโหลดคลาสจะถูกดำเนินการ (คำอธิบายจะไม่ทำที่นี่เกี่ยวกับการโหลดคลาส) หากไม่พบคลาสนี้ข้อยกเว้นของ ClassNotFoundException ทั่วไปจะถูกโยนลงไป
หลังจากการตรวจสอบการโหลดในชั้นเรียนหน่วยความจำกายภาพ (หน่วยความจำฮีป) จะถูกจัดสรรให้กับวัตถุ พื้นที่หน่วยความจำที่ต้องการโดยวัตถุจะถูกกำหนดโดยคลาสที่เกี่ยวข้อง หลังการโหลดคลาสพื้นที่หน่วยความจำที่ต้องการโดยวัตถุของคลาสนี้ได้รับการแก้ไข การจัดสรรพื้นที่หน่วยความจำสำหรับวัตถุนั้นเทียบเท่ากับการแบ่งชิ้นส่วนจากกองและจัดสรรให้กับวัตถุนี้
ตามที่พื้นที่หน่วยความจำนั้นต่อเนื่องหรือไม่ (จัดสรรและไม่ได้จัดสรรจะแบ่งออกเป็นสองส่วนที่สมบูรณ์) มันแบ่งออกเป็นสองวิธีในการจัดสรรหน่วยความจำ:
1. หน่วยความจำอย่างต่อเนื่อง: ตัวชี้ถูกใช้เป็นจุดหารระหว่างหน่วยความจำที่จัดสรรและไม่ได้ถูกปันส่วน การจัดสรรหน่วยความจำวัตถุต้องใช้ตัวชี้เพื่อย้ายขนาดพื้นที่ไปยังเซ็กเมนต์หน่วยความจำที่ไม่ได้จัดสรรเท่านั้น วิธีนี้เรียกว่า "การชนของตัวชี้"
2. หน่วยความจำไม่ต่อเนื่อง: เครื่องเสมือนจำเป็นต้องเก็บรักษา (บันทึก) รายการที่บันทึกบล็อกหน่วยความจำเหล่านั้นในกองที่ไม่ได้จัดสรร เมื่อจัดสรรหน่วยความจำวัตถุให้เลือกพื้นที่หน่วยความจำที่มีขนาดที่เหมาะสมเพื่อจัดสรรให้กับวัตถุและอัปเดตรายการนี้ วิธีนี้เรียกว่า "รายการฟรี"
การจัดสรรหน่วยความจำวัตถุจะประสบปัญหาพร้อมกัน เครื่องเสมือนใช้สองวิธีแก้ปัญหาความปลอดภัยของเธรดนี้: ก่อนอื่นใช้ CAS (เปรียบเทียบและตั้งค่า)+ เพื่อระบุและลองอีกครั้งเพื่อให้แน่ใจว่าอะตอมของการดำเนินการจัดสรร; ประการที่สองการจัดสรรหน่วยความจำแบ่งออกเป็นช่องว่างที่แตกต่างกันตามเธรดนั่นคือแต่ละเธรดที่จัดสรรไว้ล่วงหน้าชิ้นส่วนของหน่วยความจำเธรดเอกชนในฮีปเรียกว่าบัฟเฟอร์ที่จัดสรรเธรดท้องถิ่น (TLAB); เมื่อเธรดนั้นต้องการจัดสรรหน่วยความจำมันจะถูกจัดสรรโดยตรงจาก TLAB เฉพาะเมื่อมีการจัดสรร TLAB ของเธรดหลังจากการจัดสรรใหม่การดำเนินการแบบซิงโครนัสจะถูกจัดสรรจากกอง โซลูชันนี้ช่วยลดการทำงานร่วมกันของหน่วยความจำกองฮีประหว่างเธรดได้อย่างมีประสิทธิภาพ ไม่ว่าจะเป็นเครื่องเสมือนที่ใช้ TLAB ผ่านพารามิเตอร์ JVM -xx: +/- usetLab
หลังจากเสร็จสิ้นการจัดสรรหน่วยความจำนอกเหนือจากข้อมูลส่วนหัวของวัตถุเครื่องเสมือนจะเริ่มต้นพื้นที่หน่วยความจำที่จัดสรรเป็นค่าศูนย์เพื่อให้แน่ใจว่าฟิลด์ของอินสแตนซ์วัตถุสามารถใช้โดยตรงกับค่าศูนย์ที่สอดคล้องกับชนิดข้อมูลโดยไม่ต้องกำหนดค่า; จากนั้นดำเนินการวิธีการเริ่มต้นเพื่อให้การเริ่มต้นเสร็จสิ้นตามรหัสก่อนที่การสร้างวัตถุอินสแตนซ์จะเสร็จสมบูรณ์
2. เค้าโครงของวัตถุในหน่วยความจำ
ในเครื่องเสมือนฮอตสปอตวัตถุจะถูกแบ่งออกเป็นสามส่วนในหน่วยความจำ: ส่วนหัววัตถุข้อมูลอินสแตนซ์และการจัดตำแหน่งและการเติม:
ส่วนหัวของวัตถุแบ่งออกเป็นสองส่วน: ส่วนหนึ่งของ IT จัดเก็บข้อมูลรันไทม์ของวัตถุรวมถึงรหัสแฮช, อายุการสร้างการรวบรวมขยะ, สถานะล็อควัตถุ, ล็อคการถือเธรด, รหัสเธรดลำเอียง, การประทับเวลาแบบเอนเอียง, ฯลฯ ; ในเครื่องเสมือน 32 บิตและ 64 บิตส่วนหนึ่งของข้อมูลนี้มีพื้นที่ 32 บิตและ 64 บิตตามลำดับ เนื่องจากมีข้อมูลรันไทม์จำนวนมาก 32 บิตหรือ 64 บิตจึงไม่เพียงพอที่จะจัดเก็บข้อมูลทั้งหมดอย่างสมบูรณ์ดังนั้นส่วนนี้จึงถูกออกแบบมาเพื่อจัดเก็บข้อมูลรันไทม์ในรูปแบบที่ไม่ได้กำหนด แต่ใช้บิตที่แตกต่างกันเพื่อจัดเก็บข้อมูลตามสถานะของวัตถุ อีกส่วนหนึ่งเก็บตัวชี้ประเภทวัตถุชี้ไปที่คลาสของวัตถุนี้ แต่ไม่จำเป็นและข้อมูลเมตาของวัตถุไม่จำเป็นต้องได้รับการพิจารณาโดยใช้ส่วนนี้ของที่เก็บข้อมูล (จะกล่าวถึงด้านล่าง);
ข้อมูลอินสแตนซ์เป็นเนื้อหาของข้อมูลประเภทต่างๆที่กำหนดโดยวัตถุและข้อมูลที่กำหนดโดยโปรแกรมเหล่านี้จะไม่ถูกเก็บไว้ในลำดับที่กำหนด พวกเขาจะถูกกำหนดตามลำดับของนโยบายการจัดสรรและคำจำกัดความของเครื่องเสมือนจริง: ยาว/คู่, int, สั้น/ถ่าน, ไบต์/บูลีน, oop (วัตถุธรรมดา ponint) , จะเห็นได้ว่านโยบายจะถูกจัดสรรตามจำนวนสถานที่ของประเภทและประเภทเดียวกันจะจัดสรรหน่วยความจำเข้าด้วยกัน; และภายใต้ความพึงพอใจของเงื่อนไขเหล่านี้ลำดับของตัวแปรคลาสแม่จะถูกนำหน้าด้วยคลาสย่อย;
ชิ้นส่วนการเติมวัตถุไม่จำเป็นต้องมีอยู่ มันมีบทบาทเฉพาะในการจัดตำแหน่งตัวยึด ในการจัดการหน่วยความจำเครื่องเสมือนฮอตสปอตได้รับการจัดการในหน่วย 8 ไบต์ ดังนั้นเมื่อมีการจัดสรรหน่วยความจำขนาดวัตถุไม่ได้เป็นหลาย 8 และการเติมการจัดตำแหน่งจะเสร็จสมบูรณ์
3. การเข้าถึงวัตถุ <br /> ในโปรแกรม Java เราสร้างวัตถุและในความเป็นจริงเราได้รับตัวแปรประเภทอ้างอิงซึ่งเราใช้งานอินสแตนซ์ในหน่วยความจำฮีป ในข้อกำหนดของเครื่องเสมือนมีกำหนดเท่านั้นที่ประเภทการอ้างอิงเป็นข้อมูลอ้างอิงที่ชี้ไปที่วัตถุและไม่ได้ระบุว่าการอ้างอิงนี้ตั้งอยู่และเข้าถึงอินสแตนซ์ในฮีปอย่างไร ปัจจุบันในเครื่องเสมือนหลักมีสองวิธีหลักในการใช้งานการเข้าถึงวัตถุ:
1. วิธีการจัดการ: ภูมิภาคแบ่งออกเป็นหน่วยความจำฮีปเป็นพูลที่จับ ตัวแปรอ้างอิงจัดเก็บที่อยู่ที่จับของวัตถุและที่จับเก็บข้อมูลที่อยู่เฉพาะของวัตถุตัวอย่างและประเภทวัตถุ ดังนั้นส่วนหัวของวัตถุจึงไม่สามารถมีประเภทวัตถุ:
2. การเข้าถึงตัวชี้โดยตรง: ประเภทอ้างอิงโดยตรงจะจัดเก็บข้อมูลที่อยู่ของวัตถุอินสแตนซ์ในฮีป แต่สิ่งนี้ต้องการให้เค้าโครงของวัตถุอินสแตนซ์ต้องมีประเภทวัตถุ:
วิธีการเข้าถึงทั้งสองนี้มีข้อได้เปรียบของตัวเอง: เมื่อที่อยู่วัตถุมีการเปลี่ยนแปลง (การเรียงลำดับหน่วยความจำการรวบรวมขยะ) วัตถุที่จัดการการเข้าถึงตัวแปรอ้างอิงไม่จำเป็นต้องเปลี่ยนแปลง แต่เฉพาะค่าที่อยู่วัตถุในมือจับจะเปลี่ยน; ในขณะที่ใช้วิธีการเข้าถึงตัวชี้โดยตรงการอ้างอิงทั้งหมดของวัตถุนี้จำเป็นต้องได้รับการแก้ไข แต่วิธีตัวชี้สามารถลดการดำเนินการที่อยู่ได้หนึ่งครั้งและในกรณีของการเข้าถึงวัตถุจำนวนมากข้อดีของวิธีนี้จะชัดเจนยิ่งขึ้น เครื่องเสมือนฮอตสปอตใช้วิธีการเข้าถึงตัวชี้โดยตรงนี้
3. ข้อยกเว้นหน่วยความจำรันไทม์
มีข้อยกเว้นหลักสองประการที่อาจเกิดขึ้นเมื่อทำงานในโปรแกรม Java: OutofMemoryError และ Stackoverflowerror; จะเกิดอะไรขึ้นในพื้นที่หน่วยความจำนั้น? ดังที่ได้กล่าวไว้ก่อนหน้านี้ยกเว้นตัวนับโปรแกรมพื้นที่หน่วยความจำอื่น ๆ จะเกิดขึ้น ส่วนนี้ส่วนใหญ่แสดงให้เห็นถึงข้อยกเว้นในแต่ละพื้นที่หน่วยความจำผ่านรหัสอินสแตนซ์และพารามิเตอร์การเริ่มต้นเครื่องเสมือนที่ใช้กันทั่วไปจำนวนมากจะถูกใช้เพื่ออธิบายสถานการณ์ได้ดีขึ้น (วิธีเรียกใช้โปรแกรมด้วยพารามิเตอร์ไม่ได้อธิบายไว้ที่นี่)
1. หน่วยความจำ java heap memory overflow
หน่วยความจำฮีปล้นเกิดขึ้นเมื่อวัตถุถูกสร้างขึ้นหลังจากความจุของฮีปถึงความจุของฮีปสูงสุด ในโปรแกรมวัตถุถูกสร้างขึ้นอย่างต่อเนื่องและวัตถุเหล่านี้รับประกันว่าจะไม่เก็บขยะ:
/** * พารามิเตอร์เครื่องเสมือน: * -xms20m ความจุฮีปต่ำสุด * -xmx20m ความจุฮีปสูงสุด * @author hwz * */ชั้นเรียนสาธารณะ headoutofMemoryError {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// ใช้คอนเทนเนอร์ใหม่ arraylist <headoutofMemoryError> (); ในขณะที่ (จริง) {// สร้างวัตถุอย่างต่อเนื่องและเพิ่มลงในคอนเทนเนอร์ listtoholdobj.add (ใหม่ headoutofmemoryError ()); - คุณสามารถเพิ่มพารามิเตอร์เครื่องเสมือน :-XX:HeapDumpOnOutOfMemoryError เมื่อส่งข้อยกเว้น OOM ให้เครื่องเสมือนทิ้งไฟล์สแน็ปช็อตของกองปัจจุบัน คุณสามารถใช้ปัญหาข้อยกเว้นการแบ่งส่วนคำนี้ในอนาคต สิ่งนี้จะไม่ถูกอธิบายในรายละเอียด ฉันจะเขียนบล็อกเพื่ออธิบายรายละเอียดโดยใช้เครื่องมือ MAT เพื่อวิเคราะห์ปัญหาหน่วยความจำ
2. สแต็กเครื่องเสมือนจริงและวิธีการในท้องถิ่นสแต็กล้น
ในเครื่องเสมือนฮอตสปอตวิธีการทั้งสองนี้ไม่ได้ถูกนำมาใช้ร่วมกัน ตามข้อกำหนดของเครื่องเสมือนจริงข้อยกเว้นทั้งสองนี้จะเกิดขึ้นในพื้นที่หน่วยความจำทั้งสองนี้:
1. หากเธรดร้องขอความลึกของสแต็กมากกว่าความลึกสูงสุดที่อนุญาตโดยเครื่องเสมือนให้โยนข้อยกเว้น Stackoverflowerror;
2. หากเครื่องเสมือนไม่สามารถใช้สำหรับพื้นที่หน่วยความจำขนาดใหญ่เมื่อขยายพื้นที่สแต็กจะมีการโยนข้อยกเว้น outofMemoryError
มีการซ้อนทับกันระหว่างสองสถานการณ์นี้: เมื่อไม่สามารถจัดสรรพื้นที่สแต็กได้เป็นไปไม่ได้ที่จะแยกแยะว่าหน่วยความจำมีขนาดเล็กเกินไปหรือความลึกของสแต็กที่ใช้มีขนาดใหญ่เกินไป
ใช้สองวิธีในการทดสอบรหัส
1. ใช้พารามิเตอร์ -xSS เพื่อลดขนาดสแต็กเรียกวิธีการซ้ำ ๆ อย่างไม่สิ้นสุดและเพิ่มความลึกของสแต็กอย่างไม่สิ้นสุด:
/** * พารามิเตอร์เครื่องเสมือน: <br> * -xss128k ความจุสแต็ก * @author hwz * */คลาสสาธารณะ stackoverflowerror {ส่วนตัว int stackdeep = 1; / *** การเรียกซ้ำที่ไม่มีที่สิ้นสุดขยายความลึกของสแต็คการโทรอย่างไม่สิ้นสุด*/ โมฆะสาธารณะ RecursiveInvoke () {stackdeep ++; RecursiveInvoke (); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {stackoverflowerror soe = new Stackoverflowerror (); ลอง {soe.recursiveInvoke (); } catch (throwable e) {system.out.println ("stack deep =" + soe.stackdeep); โยน e; - ตัวแปรท้องถิ่นจำนวนมากถูกกำหนดไว้ในวิธีการความยาวของตารางตัวแปรท้องถิ่นในสแต็กวิธีการเรียกอีกอย่างว่าไม่สิ้นสุด:
/** * @author hwz * */คลาสสาธารณะ stackoomeerror {ส่วนตัว int stackdeep = 1; / *** กำหนดตัวแปรท้องถิ่นจำนวนมากเพิ่มตารางตัวแปรท้องถิ่นในสแต็ก* การเรียกซ้ำที่ไม่มีที่สิ้นสุดเพิ่มความลึกของสแต็กการโทร*/ โมฆะสาธารณะซ้ำอีกครั้ง () {double i; สอง i2; //.AON. คำจำกัดความตัวแปรจำนวนมากถูกละไว้ที่นี่ stackdeep ++; RecursiveInvoke (); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {stackoomeerror soe = ใหม่ stackoomeerror (); ลอง {soe.recursiveInvoke (); } catch (throwable e) {system.out.println ("stack deep =" + soe.stackdeep); โยน e; -การทดสอบรหัสด้านบนแสดงให้เห็นว่าไม่ว่าเฟรมสแต็กจะมีขนาดใหญ่เกินไปหรือความจุของเครื่องเสมือนมีขนาดเล็กเกินไปเมื่อไม่สามารถจัดสรรหน่วยความจำ Stackoverflowerror ทั้งหมดถูกโยนลงไป
3. พื้นที่วิธีการและรันไทม์คงที่พูลล้น
ที่นี่เราจะอธิบายวิธีการฝึกงานของสตริงก่อน: หากพูลคงที่สตริงคงที่มีสตริงเท่ากับวัตถุสตริงนี้แล้วมันจะส่งคืนวัตถุสตริงที่แสดงสตริงนี้ มิฉะนั้นเพิ่มวัตถุสตริงนี้ลงในพูลคงที่และส่งคืนการอ้างอิงไปยังวัตถุสตริงนี้ ด้วยวิธีนี้มันจะเพิ่มวัตถุสตริงลงในพูลคงที่อย่างต่อเนื่องส่งผลให้ล้น:
/** * พารามิเตอร์เครื่องเสมือน: <br> * -xx: permsize = 10m ขนาดพื้นที่ถาวร * -xx: maxpermsize = 10m พื้นที่ถาวรความจุสูงสุด * @author hwz * */คลาสสาธารณะ runtimeconstancepooloom ArrayList <String> (); // ใช้เมธอด string.intern เพื่อเพิ่มวัตถุของพูลคงที่สำหรับ (int i = 1; true; i ++) {list.add (string.valueof (i) .intern ()); -อย่างไรก็ตามรหัสทดสอบนี้ไม่ได้ล้นในระหว่างพูลคงที่รันไทม์ใน JDK1.7 แต่มันจะเกิดขึ้นใน JDK1.6 ด้วยเหตุผลนี้เขียนรหัสทดสอบอื่นเพื่อตรวจสอบปัญหานี้:
/** * String.intern วิธีการทดสอบภายใต้ jdks ที่แตกต่างกัน * @author hwz * */คลาสสาธารณะ stringInternTest {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {string str1 = สตริงใหม่ ("ทดสอบ") ผนวก ("01"). toString (); System.out.println (str1.intern () == str1); string str2 = ใหม่ stringbuilder ("ทดสอบ"). ผนวก ("02"). toString (); System.out.println (str2.intern () == str2); - ผลลัพธ์ของการทำงานภายใต้ JDK1.6 คือ: เท็จเท็จ;
ผลลัพธ์ของการทำงานภายใต้ JDK1.7 คือ: จริงจริง;
ปรากฎว่าใน JDK1.6 วิธีการฝึกงาน () คัดลอกอินสแตนซ์สตริงที่พบครั้งแรกไปยังรุ่นถาวรซึ่งจะเป็นการอ้างอิงถึงอินสแตนซ์ในรุ่นถาวรและอินสแตนซ์สตริงที่สร้างโดย StringBuilder อยู่ในกองดังนั้นจึงไม่เท่ากัน
ใน JDK1.7 วิธีการฝึกงาน () ไม่ได้คัดลอกอินสแตนซ์ แต่จะบันทึกเฉพาะการอ้างอิงของอินสแตนซ์แรกที่ปรากฏในพูลคงที่ ดังนั้นการอ้างอิงที่ส่งคืนโดยผู้ฝึกงานจึงเหมือนกับอินสแตนซ์ที่สร้างโดย StringBuilder ดังนั้นจึงส่งคืนจริง
ดังนั้นรหัสทดสอบสำหรับการล้นพูลคงที่จะไม่มีข้อยกเว้นสระว่ายน้ำคงที่อย่างต่อเนื่อง แต่อาจมีหน่วยความจำที่ไม่เพียงพอที่จะมีการยกเว้นการไหลล้นฮีปไม่เพียงพอหลังจากวิ่งอย่างต่อเนื่อง
จากนั้นคุณต้องทดสอบการล้นของพื้นที่วิธีการเพียงแค่เพิ่มสิ่งต่าง ๆ ลงในพื้นที่วิธีการเช่นชื่อคลาสตัวดัดแปลงการเข้าถึงพูลคงที่ ฯลฯ เราสามารถปล่อยให้โปรแกรมโหลดคลาสจำนวนมากเพื่อเติมเต็มพื้นที่วิธีการอย่างต่อเนื่องซึ่งนำไปสู่การล้น เราใช้ CGLIB เพื่อจัดการ bytecode โดยตรงเพื่อสร้างคลาสไดนามิกจำนวนมาก:
/** * เมธอดพื้นที่หน่วยความจำการทดสอบล้น * @author hwz * */คลาสสาธารณะเมธอเรียม {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// ใช้ gclib เพื่อสร้าง subclasses อย่างไม่สิ้นสุดในขณะที่ (จริง) {enhancer enhancer = ใหม่ enhancer.setsuperclass (maoomclass.class); enhancer.setUsecache (เท็จ); enhancer.setCallback (New MethodInterceptor () {@Override การสกัดกั้นวัตถุสาธารณะ (Object OBJ, วิธีการ, วัตถุ [] args, methodproxy proxy) โยน {return proxy.invokesuper (obj, args);}}); enhancer.create (); }} คลาสคงที่ maoomclass {}} ผ่านการสังเกต VisualVM เราจะเห็นว่าจำนวนคลาสที่โหลด JVM เพิ่มขึ้นเป็นเส้นตรงด้วยการใช้ Pergen:
4. หน่วยความจำโดยตรงล้น
ขนาดของหน่วยความจำโดยตรงสามารถตั้งค่าผ่านพารามิเตอร์เครื่องเสมือน : -xx: MaxDirectMemorySize ในการทำให้หน่วยความจำโดยตรงล้นคุณจะต้องสมัครอย่างต่อเนื่องสำหรับหน่วยความจำโดยตรง ต่อไปนี้เหมือนกับการทดสอบแคชหน่วยความจำโดยตรงใน Java Nio:
/** * พารามิเตอร์เครื่องเสมือน: <br> * -xx: MaxDirectMemorySize = 30M ขนาดหน่วยความจำโดยตรง * @author HWZ * */คลาสสาธารณะ DirectMemoryoom {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {รายการ <buffer> บัฟเฟอร์ = ใหม่ int i = 0; ในขณะที่ (จริง) {// พิมพ์ system.out.out.println (++ i); // การใช้หน่วยความจำโดยตรงโดยการใช้อย่างต่อเนื่องสำหรับการใช้หน่วยความจำบัฟเฟอร์โดยตรงในแคชบัฟเฟอร์ ADD (byteBuffer.Allocatedirect (1024*1024)); // การบัญชี 1m แต่ละครั้ง}}} ในลูปแต่ละหน่วยความจำโดยตรง 1M จะถูกนำไปใช้หน่วยความจำโดยตรงสูงสุดจะถูกตั้งค่าเป็น 30m และมีข้อยกเว้นถูกโยนลงเมื่อโปรแกรมทำงาน 31 ครั้ง: java.lang.OutOfMemoryError: Direct buffer memory
4. สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ บทความนี้ส่วนใหญ่อธิบายโครงสร้างเค้าโครงของหน่วยความจำการจัดเก็บวัตถุและข้อยกเว้นหน่วยความจำที่อาจเกิดขึ้นในพื้นที่หน่วยความจำต่างๆใน JVM; หนังสืออ้างอิงหลัก "ความเข้าใจในเชิงลึกของ Java Virtual Machine (รุ่นที่สอง)" หากมีความไม่ถูกต้องโปรดชี้ให้เห็นในความคิดเห็น ขอบคุณสำหรับการสนับสนุน Wulin.com