ไดอะแกรมโครงสร้างภายใน JVM
เครื่องเสมือน Java ส่วนใหญ่แบ่งออกเป็นห้าพื้นที่: พื้นที่วิธี, heap, java stack, PC register และ method method สแต็ค มาดูประเด็นสำคัญบางประการเกี่ยวกับโครงสร้าง JVM
1. มีการแบ่งปันพื้นที่ใด คนไหนเป็นส่วนตัว?
สแต็ค Java, Method Method Local และตัวนับโปรแกรมได้รับการจัดตั้งและทำลายเมื่อเธรดผู้ใช้เริ่มต้นและสิ้นสุด
แต่ละเธรดมีพื้นที่อิสระเหล่านี้ พื้นที่วิธีการและกองถูกแชร์โดยเธรดทั้งหมดในกระบวนการ JVM ทั้งหมด
2. บันทึกอะไรในพื้นที่วิธีการ? จะรีไซเคิลหรือไม่?
พื้นที่วิธีการไม่เพียง แต่บันทึกข้อมูลวิธีการและรหัส ในเวลาเดียวกันในภูมิภาคย่อยที่เรียกว่าพูลคงที่รันไทม์การอ้างอิงเชิงสัญลักษณ์ต่างๆในตารางคงที่ในไฟล์คลาสรวมถึงการอ้างอิงโดยตรงที่แปล ข้อมูลนี้เข้าถึงได้ผ่านวัตถุคลาสในฮีปเป็นอินเทอร์เฟซ
แม้ว่าข้อมูลประเภทจะถูกเก็บไว้ในพื้นที่วิธีการ แต่ก็จะถูกนำกลับมาใช้ใหม่ แต่เงื่อนไขการรีไซเคิลค่อนข้างเข้มงวด:
(1) ทุกกรณีของคลาสนี้ได้รับการรีไซเคิล
(2) classloader โหลดคลาสนี้ได้รับการรีไซเคิล
(3) วัตถุคลาสของคลาสนี้ไม่ได้อ้างอิงทุกที่ (รวมถึงคลาสการเข้าถึงการสะท้อนกลับชื่อ)
3. เนื้อหาของสระคงที่ในพื้นที่วิธีการไม่เปลี่ยนแปลงหรือไม่?
พูลคงที่รันไทม์ในพื้นที่วิธีการบันทึกข้อมูลในพูลคงที่คงที่ในไฟล์คลาส นอกเหนือจากการจัดเก็บข้อมูลอ้างอิงตามตัวอักษรและสัญลักษณ์ต่างๆที่สร้างขึ้นในเวลารวบรวมแล้วยังมีการอ้างอิงโดยตรงที่แปล แต่นี่ไม่ได้หมายความว่ากลุ่มคงที่จะไม่เปลี่ยนแปลงในระหว่างการรันไทม์ ตัวอย่างเช่นเมื่อทำงานคุณสามารถเรียกวิธีการฝึกงานของสตริงและใส่ค่าคงที่สตริงใหม่ลงในพูล
แพ็คเกจ com.cdai.jvm; คลาสสาธารณะ runtimeconstantpool {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {สตริง s1 = สตริงใหม่ ("สวัสดี"); สตริง s2 = สตริงใหม่ ("สวัสดี"); System.out.println ("ก่อนฝึกงาน, S1 == S2:" + (S1 == S2)); s1 = s1.intern (); s2 = s2.intern (); System.out.println ("หลังฝึกงาน, S1 == S2:" + (S1 == S2)); -
4. อินสแตนซ์ของวัตถุทั้งหมดจัดสรรบนกองหรือไม่?
ด้วยการครบกำหนดอย่างค่อยเป็นค่อยไปของเทคโนโลยีการวิเคราะห์การหลบหนีการจัดสรรบนซ้อนและเทคโนโลยีการเพิ่มประสิทธิภาพการทดแทนสเกลาร์ได้ทำ "วัตถุทั้งหมดที่จัดสรรบนฮีป" สัมบูรณ์น้อยลง
การหลบหนีที่เรียกว่าหมายถึงเมื่อตัวชี้ของวัตถุถูกอ้างอิงโดยหลายวิธีหรือเธรดเราบอกว่าตัวชี้นี้หนีไปได้
โดยทั่วไปแล้ววัตถุ Java จะถูกจัดสรรในกองและมีเพียงพอยน์เตอร์ของวัตถุเท่านั้นที่ถูกบันทึกไว้ในสแต็ก สมมติว่าตัวแปรท้องถิ่นไม่หลบหนีในระหว่างการดำเนินการของวิธีการ (สัมผัสกับด้านนอกของวิธีการ) มันจะถูกจัดสรรโดยตรงบนสแต็กแล้วดำเนินการต่อในสแต็กการโทรต่อไป หลังจากการดำเนินการของวิธีการพื้นที่สแต็กจะถูกรีไซเคิลและตัวแปรท้องถิ่นจะถูกนำกลับมาใช้ใหม่ สิ่งนี้จะช่วยลดการจัดสรรวัตถุชั่วคราวจำนวนมากในกองและปรับปรุงประสิทธิภาพของการรีไซเคิล GC
นอกจากนี้การวิเคราะห์การหลบหนีจะละเว้นการล็อคตัวแปรท้องถิ่นที่ไม่ได้หลบหนีและละเว้นล็อคที่เป็นเจ้าของในตัวแปรนี้
เมื่อเปิดใช้งานวิธีการวิเคราะห์หลบหนีให้เพิ่มพารามิเตอร์การเริ่มต้น JVM: -xx: +DoingCapeanalysis? escapeanalysistest
5. มีกี่วิธีในการเข้าถึงวัตถุบนกอง?
(1) การเข้าถึงตัวชี้โดยตรง
การอ้างอิงบนสแต็กจะบันทึกตัวชี้ไปยังวัตถุบนกองและคุณสามารถค้นหาวัตถุในครั้งเดียวซึ่งมีความเร็วในการเข้าถึงที่เร็วขึ้น
อย่างไรก็ตามเมื่อวัตถุถูกเคลื่อนย้ายในกอง (แต่ละวัตถุมักจะถูกย้ายระหว่างการรวบรวมขยะ) ค่าของตัวแปรตัวชี้บนสแต็กจะต้องมีการเปลี่ยนแปลง ปัจจุบัน JVM Hotspot ใช้วิธีนี้
(2) การเข้าถึงทางอ้อมเพื่อจัดการ
การอ้างอิงบนจุดสแต็กไปยังที่จับในพูลด้ามจับและวัตถุสามารถเข้าถึงได้ผ่านค่าในมือจับนี้ ดังนั้นที่จับจึงเป็นเหมือนตัวชี้ทุติยภูมิซึ่งต้องใช้สองตำแหน่งในการเข้าถึงวัตถุซึ่งช้ากว่าการวางตำแหน่งตัวชี้โดยตรง แต่เมื่อวัตถุเคลื่อนที่ในฮีปไม่จำเป็นต้องเปลี่ยนค่าที่อ้างอิงบนสแต็ก
วิธีการล้น JVM
หลังจากทำความเข้าใจบทบาทของพื้นที่หน่วยความจำทั้งห้าของเครื่องเสมือน Java ให้เรียนรู้ต่อไปภายใต้สถานการณ์ที่พื้นที่เหล่านี้จะล้น
1. การกำหนดค่าพารามิเตอร์เครื่องเสมือน
-XMS: ขนาดฮีปเริ่มต้นค่าเริ่มต้นคือ 1/64 ของหน่วยความจำกายภาพ (<1GB); ค่าเริ่มต้น (พารามิเตอร์ MinHeapFreerAtio สามารถปรับได้) เมื่อหน่วยความจำฮีปฟรีน้อยกว่า 40%JVM จะเพิ่มกองจนกว่าจะถึงขีด จำกัด สูงสุด -xmx
-XMX: ขนาดฮีปสูงสุดค่าเริ่มต้น (พารามิเตอร์ MaxHeapFreerAtio สามารถปรับได้) เมื่อหน่วยความจำฮีปฟรีมากกว่า 70%JVM จะลดกองจนกว่าขีด จำกัด ขั้นต่ำ -xms
-XSS: ขนาดสแต็กสำหรับแต่ละเธรด หลังจาก JDK5.0 ขนาดสแต็กของแต่ละเธรดคือ 1m และในอดีตขนาดสแต็กของแต่ละเธรดคือ 256k ควรปรับอย่างเหมาะสมตามขนาดหน่วยความจำที่ต้องการของเธรดของแอปพลิเคชัน ในหน่วยความจำทางกายภาพเดียวกันการลดค่านี้สามารถสร้างเธรดได้มากขึ้น อย่างไรก็ตามระบบปฏิบัติการยังคงมีข้อ จำกัด เกี่ยวกับจำนวนเธรดในกระบวนการและไม่สามารถสร้างได้อย่างไม่สิ้นสุดด้วยค่าประสบการณ์ตั้งแต่ประมาณ 3,000 ถึง 5,000 โดยทั่วไปแอปพลิเคชันขนาดเล็กหากสแต็กไม่ลึกมาก 128K ควรเพียงพอ สำหรับแอปพลิเคชั่นขนาดใหญ่ขอแนะนำ 256K ตัวเลือกนี้มีผลกระทบอย่างมากต่อประสิทธิภาพและต้องมีการทดสอบอย่างเข้มงวด
-xx: Permsize: ตั้งค่าการสร้างถาวร (Perm Gen) ค่าเริ่มต้น ค่าเริ่มต้นคือ 1/64 ของหน่วยความจำกายภาพ
-xx: Maxpermsize: ตั้งค่าสูงสุดของการสร้างแบบถาวร 1/4 ของหน่วยความจำทางกายภาพ
2. วิธีการล้นพื้นที่
เนื่องจากพื้นที่วิธีการเก็บข้อมูลที่เกี่ยวข้องของชั้นเรียนเมื่อเราโหลดคลาสมากเกินไปพื้นที่วิธีการจะล้น ที่นี่เราพยายามที่จะล้นพื้นที่วิธีการผ่านสองวิธี: JDK พร็อกซีไดนามิกและพร็อกซี CGLIB
2.1 JDK Dynamic Proxy
แพ็คเกจ com.cdai.jvm.overflow; นำเข้า java.lang.reflect.invocationhandler; นำเข้า java.lang.reflect.method; นำเข้า java.lang.reflect.proxy; คลาสสาธารณะ methodareaoverflow {อินเตอร์เฟสคงที่ oomiinterface {} คลาสคงที่ oomobject ดำเนินการ oomiinterface {} คลาสคงที่ oomobject2 ใช้ oomiinterface {} โมฆะคงที่สาธารณะ (สตริง [] args) ในขณะที่ (true) {oomiinterface proxy = (oomiinterface) proxy.newproxyinstance (thread.currentthread (). getContextclassLoader (), oomobject.class.getinterfaces (), argocationhandler System.out.println ("Interceptor1 กำลังทำงาน"); System.out.println (Proxy.getClass ()); System.out.println ("Proxy1:" + พร็อกซี); oomiinterface proxy2 = (oomiinterface) proxy.newproxyinstance (thread.currentthread (). getContextClassloader (), oomobject.class.getInterfaces (), argicationHandler ใหม่ (@Override System.out.println ("Interceptor2 ทำงาน"); System.out.println (Proxy2.getClass ()); System.out.println ("Proxy2:" + Proxy2); - แม้ว่าเราจะเรียกเมธอด Proxy.NewInstance () เพื่อสร้างคลาสพร็อกซี แต่ JVM ก็ไม่มีหน่วยความจำล้น
การโทรแต่ละครั้งจะสร้างอินสแตนซ์ที่แตกต่างกันของคลาสพร็อกซี แต่วัตถุคลาสของคลาสพร็อกซีไม่เปลี่ยนแปลง เป็นพร็อกซี
คลาสมีแคชสำหรับวัตถุคลาสของคลาสพร็อกซีหรือไม่? เหตุผลเฉพาะจะถูกวิเคราะห์ในรายละเอียดใน "JDK Dynamic Proxy และ CGLIB" ที่ตามมา
2.2 CGLIB Agent
CGLIB จะแคชวัตถุคลาสของคลาสพร็อกซี แต่เราสามารถกำหนดค่าให้ไม่แคชวัตถุคลาส
ด้วยวิธีนี้จุดประสงค์ของการล้นพื้นที่วิธีการสามารถทำได้โดยการสร้างคลาสพร็อกซีซ้ำ ๆ
แพ็คเกจ com.cdai.jvm.overflow; นำเข้า java.lang.reflect.method; นำเข้า net.sf.cglib.proxy.enhancer; นำเข้า net.sf.cglib.proxy.methodinterceptor; นำเข้า net.sf.cglib.proxy.methodproxy; คลาสสาธารณะ MethodAreAoverFlow2 {คลาสคงที่ oomobject {} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ในขณะที่ (จริง) {enhancer enhancer = ใหม่ enhancer (); enhancer.setsuperclass (oomobject.class); enhancer.setUsecache (เท็จ); enhancer.setCallback (New MethodInterceptor () {@Override การสกัดกั้นวัตถุสาธารณะ (Object OBJ, วิธีการ, วัตถุ [] args, methodproxy proxy) โยน {วิธีการส่งคืนที่สามารถโยนได้ oomobject proxy = (oomobject) enhancer.create (); System.out.println (Proxy.getClass ()); -
3. กองล้น
ฮีปล้นค่อนข้างง่าย คุณสามารถล้นกองได้ด้วยการสร้างวัตถุอาร์เรย์ขนาดใหญ่
แพ็คเกจ com.cdai.jvm.overflow; ชั้นเรียนสาธารณะ heapoverflow {ส่วนตัวคงที่ int mb = 1024 * 1024; @suppresswarnings ("ไม่ได้ใช้") โมฆะคงที่สาธารณะหลัก (String [] args) {byte [] bigmemory = byte ใหม่ [1024 * mb]; -
4. สแต็คล้น
สแต็คล้นก็เป็นเรื่องธรรมดา บางครั้งเมื่อการโทรแบบเรียกซ้ำที่เราเขียนไม่มีเงื่อนไขการเลิกจ้างที่ถูกต้องวิธีการจะกลับมาอีกครั้งความลึกของสแต็กจะเพิ่มขึ้นเรื่อย ๆ และในที่สุดสแต็กล้นจะเกิดขึ้น
แพ็คเกจ com.cdai.jvm.overflow; ระดับสาธารณะ stackoverflow {ส่วนตัวคงที่ int stackdepth = 1; โมฆะสาธารณะคงที่ stackoverflow () {stackdepth ++; stackoverflow (); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ลอง {stackoverflow (); } catch (exception e) {system.err.println ("ความลึกของสแต็ก:" + stackdepth); E.PrintStackTrace (); -