ตั้งแต่งานมีการเขียนโค้ดมากขึ้นเรื่อย ๆ โปรแกรมได้กลายเป็นป่องมากขึ้นเรื่อย ๆ และประสิทธิภาพก็น้อยลงเรื่อย ๆ นี่ไม่ได้รับอนุญาตอย่างแน่นอนสำหรับโปรแกรมเมอร์อย่างฉันที่แสวงหาความสมบูรณ์แบบ ดังนั้นนอกเหนือจากการเพิ่มประสิทธิภาพโครงสร้างโปรแกรมการเพิ่มประสิทธิภาพหน่วยความจำและการปรับแต่งประสิทธิภาพได้กลายเป็น "เทคนิค" ตามปกติของฉัน
เพื่อเพิ่มประสิทธิภาพและปรับแต่งหน่วยความจำและประสิทธิภาพของโปรแกรม Java มันเป็นไปไม่ได้ที่จะไม่เข้าใจหลักการภายในของเครื่องเสมือน (หรือข้อมูลจำเพาะที่เข้มงวดมากขึ้น) นี่คือหนังสือที่ดี "เครื่องเสมือน Java ในเชิงลึก (ฉบับที่สอง)" (โดย Bill Venners แปลโดย Cao Xiaogang และ Jiang Jing ในความเป็นจริงบทความนี้เป็นความเข้าใจส่วนตัวของผู้เขียนเกี่ยวกับเครื่องเสมือน Java หลังจากอ่านหนังสือเล่มนี้) แน่นอนว่าประโยชน์ของการทำความเข้าใจเครื่องเสมือน Java ไม่ได้ จำกัด อยู่ที่ผลประโยชน์สองประการข้างต้น จากมุมมองทางเทคนิคที่ลึกซึ้งยิ่งขึ้นการทำความเข้าใจข้อกำหนดและการใช้งานเครื่องเสมือน Java จะมีประโยชน์มากขึ้นสำหรับเราในการเขียนรหัส Java ที่มีประสิทธิภาพและมีเสถียรภาพ ตัวอย่างเช่นหากเราเข้าใจโมเดลหน่วยความจำของเครื่องเสมือน Java และกลไกการรีไซเคิลหน่วยความจำของเครื่องเสมือนเราจะไม่พึ่งพามันมากเกินไป แต่จะ "ปล่อยหน่วยความจำ" อย่างชัดเจนเมื่อจำเป็น (รหัส Java ไม่สามารถปล่อยหน่วยความจำได้อย่างชัดเจน หากเราเข้าใจว่า Java Stack ทำงานอย่างไรเราสามารถลดความเสี่ยงของการล้นสแต็กได้โดยการลดจำนวนเลเยอร์แบบเรียกซ้ำและจำนวนลูป สำหรับนักพัฒนาแอปพลิเคชันพวกเขาอาจไม่เกี่ยวข้องโดยตรงกับการทำงานของการดำเนินการพื้นฐานของเครื่องเสมือนจริง Java เหล่านี้ แต่การเข้าใจความรู้พื้นฐานนี้จะมีผลกระทบเล็กน้อยและดีต่อโปรแกรมที่เราเขียน
บทความนี้จะอธิบายสั้น ๆ เกี่ยวกับสถาปัตยกรรมและโมเดลหน่วยความจำของเครื่องเสมือน Java หากมีคำที่ไม่เหมาะสมหรือคำอธิบายที่ไม่ถูกต้องโปรดตรวจสอบให้แน่ใจว่าได้แก้ไข ฉันรู้สึกเป็นเกียรติมาก!
สถาปัตยกรรมเครื่องเสมือน Java
ระบบย่อยการโหลดคลาส
มีตัวโหลดคลาสสองชั้นสำหรับเครื่องเสมือน Java คือตัวโหลดคลาสเริ่มต้นและตัวโหลดที่ผู้ใช้กำหนด
ระบบย่อยการโหลดคลาสโหลดคลาสลงในพื้นที่ข้อมูลรันไทม์ผ่านชื่อที่ผ่านการรับรองของคลาส (ชื่อแพ็คเกจและชื่อคลาสการเมาท์เครือข่ายยังมี URL) สำหรับแต่ละประเภทที่โหลดเครื่องเสมือน Java จะสร้างอินสแตนซ์ของคลาส java.lang.class เพื่อแสดงประเภทซึ่งอยู่ในพื้นที่กองในหน่วยความจำและข้อมูลประเภทที่โหลดอยู่ในพื้นที่เมธอดซึ่งเหมือนกับวัตถุอื่น ๆ ทั้งหมด
ก่อนที่จะโหลดประเภทระบบย่อยการโหลดคลาสจะต้องไม่เพียง แต่ค้นหาและนำเข้าไฟล์คลาสไบนารีที่เกี่ยวข้อง แต่ยังตรวจสอบความถูกต้องของคลาสที่นำเข้าจัดสรรและเริ่มต้นหน่วยความจำสำหรับตัวแปรคลาสและการอ้างอิงสัญลักษณ์การแยกวิเคราะห์เป็นการอ้างอิงโดยตรง การกระทำเหล่านี้อยู่ในลำดับต่อไปนี้อย่างเคร่งครัด:
1) การโหลด - ค้นหาและโหลดข้อมูลไบนารีของประเภท;
2) การเชื่อมต่อ - ดำเนินการตรวจสอบการเตรียมการและการแยกวิเคราะห์ (ไม่บังคับ)
3) ตรวจสอบเพื่อให้แน่ใจว่าความถูกต้องของประเภทที่นำเข้า
4) เตรียมที่จะจัดสรรหน่วยความจำสำหรับตัวแปรคลาสและเริ่มต้นเป็นค่าเริ่มต้น
5) วิเคราะห์การอ้างอิงเชิงสัญลักษณ์ในประเภทการใช้งานโดยตรง
พื้นที่วิธีการ
สำหรับแต่ละประเภทที่โหลดโดยระบบย่อยการโหลดคลาสเครื่องเสมือนจะบันทึกข้อมูลต่อไปนี้ไปยังพื้นที่เมธอด:
1. ชื่อประเภทที่มีคุณสมบัติครบถ้วน
2. ชื่อที่ผ่านการรับรองอย่างสมบูรณ์ของประเภท superclass (java.lang.Object ไม่มี superclass)
3. คือประเภทคลาสประเภท A หรือประเภทอินเตอร์เฟส
4. ประเภทการเข้าถึงการเข้าถึง
5. รายการชื่อที่สั่งซื้อเต็มรูปแบบของรายการ hyperinterface โดยตรงใด ๆ
นอกเหนือจากข้อมูลประเภทพื้นฐานด้านบนแล้วข้อมูลต่อไปนี้จะถูกบันทึกไว้ด้วย:
6. พิมพ์พูลคงที่
7. ข้อมูลฟิลด์ (รวมถึงชื่อฟิลด์ประเภทฟิลด์ตัวดัดแปลงฟิลด์)
8. ข้อมูลวิธีการ (รวมถึงชื่อวิธี, ประเภทการส่งคืน, หมายเลขและประเภทของพารามิเตอร์, ตัวดัดแปลงวิธีการหากวิธีการไม่เป็นนามธรรมและท้องถิ่นวิธีการ bytecode, ตัวถูกดำเนินการสแต็กและขนาดและตารางข้อยกเว้นของพื้นที่ตัวแปรท้องถิ่นในเฟรมสแต็กเมธอดก็จะถูกบันทึกด้วย)
9. ตัวแปรคลาสทั้งหมดยกเว้นค่าคงที่ (ที่จริงแล้วพวกเขาเป็นตัวแปรคงที่ของคลาสเนื่องจากตัวแปรคงที่ถูกใช้ร่วมกันโดยทุกกรณีและเกี่ยวข้องโดยตรงกับประเภทพวกเขาเป็นตัวแปรระดับชั้นเรียนและบันทึกไว้ในพื้นที่วิธีการเป็นสมาชิกของชั้นเรียน)
10. การอ้างอิงถึง classloader
// ที่ส่งคืนคือสตริงอ้างอิงคลาสโหลด class.getClassLoader () ที่บันทึกไว้ตอนนี้; การอ้างอิงไปยังคลาสคลาส // มันจะส่งคืนสตริงอ้างอิงคลาสของคลาสคลาสเพิ่งบันทึกไว้ตอนนี้;
โปรดทราบว่าพื้นที่วิธีการสามารถนำไปรีไซเคิลได้โดยนักสะสมขยะ
กอง
อินสแตนซ์หรืออาร์เรย์ของคลาสทั้งหมดที่สร้างโดยโปรแกรม Java ที่รันไทม์จะถูกวางไว้ในกองเดียวกันและเครื่องเสมือน Java แต่ละเครื่องยังมีพื้นที่ฮีปและเธรดทั้งหมดแบ่งปันกอง (นี่คือเหตุผลที่โปรแกรม Java แบบมัลติเธรดจะทำให้เกิดปัญหาการซิงโครไนซ์ในการเข้าถึงวัตถุ)
เนื่องจากเครื่องเสมือน Java แต่ละเครื่องมีการใช้งานที่แตกต่างกันของข้อกำหนดของเครื่องเสมือนเราจึงอาจไม่ทราบว่ารูปแบบของเครื่องเสมือน Java แต่ละตัวแสดงถึงอินสแตนซ์ของวัตถุในฮีป แต่เราสามารถมองเห็นการใช้งานที่เป็นไปได้ต่อไปนี้:
เคาน์เตอร์โปรแกรม
สำหรับการเรียกใช้โปรแกรม Java แต่ละเธรดมีการลงทะเบียนพีซี (ตัวนับโปรแกรม) ซึ่งสร้างขึ้นเมื่อเธรดเริ่มต้นด้วยขนาดหนึ่งคำและใช้เพื่อบันทึกตำแหน่งของรหัสบรรทัดถัดไปที่ต้องดำเนินการ
จาวาสแต็ค
แต่ละเธรดมีสแต็ค Java ซึ่งจะช่วยประหยัดสถานะการทำงานของเธรดในหน่วยของเฟรมสแต็ก มีการใช้งานสองประเภทของเครื่องเสมือนจริงบน Java Stack: การกดสแต็กและการซ้อนซึ่งทั้งสองอย่างมีเฟรม เฟรมสแต็กจะบันทึกข้อมูลเช่นพารามิเตอร์ที่เข้ามาตัวแปรท้องถิ่นผลการดำเนินงานระดับกลาง ฯลฯ ซึ่งจะปรากฏขึ้นเมื่อวิธีการเสร็จสิ้นแล้วปล่อยออกมา
ลองดูสแนปชอตหน่วยความจำของเฟรมสแต็กเมื่อเพิ่มตัวแปรสองตัวในท้องถิ่นเข้าด้วยกัน
สแต็ควิธีการท้องถิ่น
นี่คือที่ที่ Java เรียกว่าไลบรารีท้องถิ่นระบบปฏิบัติการใช้เพื่อใช้ JNI (อินเตอร์เฟส Java Native, Java Local Interface)
เอ็นจินดำเนินการ
แกนกลางของเครื่องเสมือน Java ควบคุมการโหลด Java bytecode และการแยกวิเคราะห์; สำหรับการเรียกใช้โปรแกรม Java แต่ละเธรดเป็นอินสแตนซ์ของเอ็นจิ้นการทำงานของเครื่องเสมือนจริง ตั้งแต่ต้นจนถึงจุดสิ้นสุดของวงจรชีวิตเธรดมันเป็นทั้งการดำเนินการ bytecode หรือดำเนินการวิธีการในท้องถิ่น
อินเทอร์เฟซท้องถิ่น
เชื่อมต่อกับสแต็กวิธีการท้องถิ่นและไลบรารีระบบปฏิบัติการ
หมายเหตุ: สถานที่ทั้งหมดที่กล่าวถึงในบทความอ้างถึง "ข้อกำหนดของเครื่องเสมือน Java สำหรับแพลตฟอร์ม Javaee และ Javase"
วิธีการเพิ่มประสิทธิภาพหน่วยความจำเสมือนจริงของเครื่องจักร
เนื่องจากหน่วยความจำถูกกล่าวถึงการรั่วไหลของหน่วยความจำจะต้องมีการกล่าวถึง อย่างที่เราทราบกันดีว่า Java พัฒนาจากพื้นฐานของ C ++ และปัญหาใหญ่กับโปรแกรม C ++ คือการรั่วไหลของหน่วยความจำนั้นยากที่จะแก้ไข แม้ว่า JVM ของ Java จะมีกลไกการรวบรวมขยะของตัวเองในการรีไซเคิลหน่วยความจำในหลายกรณีนักพัฒนาโปรแกรม Java ไม่จำเป็นต้องกังวลมากเกินไป แต่ก็มีปัญหารั่วไหลซึ่งเล็กกว่า C ++ เล็กน้อย ตัวอย่างเช่นมีวัตถุอ้างอิง แต่ไร้ประโยชน์ในโปรแกรม: หากโปรแกรมอ้างอิงวัตถุ แต่จะไม่หรือไม่สามารถใช้งานได้ในอนาคตพื้นที่หน่วยความจำที่ใช้จะสูญเปล่า
ก่อนอื่นให้ดูวิธีการทำงานของ GC: ตรวจสอบสถานะการทำงานของแต่ละวัตถุรวมถึงแอปพลิเคชันการอ้างอิงการอ้างอิงการมอบหมาย ฯลฯ เมื่อวัตถุไม่ได้อ้างถึงอีกต่อไปปล่อยวัตถุ (โฟกัสของ GC บทความนี้จะไม่อธิบายมากเกินไป) โปรแกรมเมอร์ Java หลายคนพึ่งพา GC มากเกินไป แต่กุญแจสำคัญในการแก้ไขปัญหาคือไม่ว่ากลไกการรวบรวมขยะของ JVM จะดีแค่ไหนความทรงจำนั้นเป็นทรัพยากรที่ จำกัด เสมอ ดังนั้นแม้ว่า GC จะเสร็จสิ้นการรวบรวมขยะส่วนใหญ่สำหรับเรา แต่ก็ยังจำเป็นต้องให้ความสนใจกับการเพิ่มประสิทธิภาพหน่วยความจำในระหว่างกระบวนการเข้ารหัสอย่างเหมาะสม สิ่งนี้สามารถลดจำนวน GCs ได้อย่างมีประสิทธิภาพในขณะที่ปรับปรุงการใช้หน่วยความจำและการเพิ่มประสิทธิภาพของโปรแกรมให้สูงสุด
โดยรวมแล้วการเพิ่มประสิทธิภาพหน่วยความจำของเครื่องเสมือน Java ควรเริ่มต้นจากสองด้าน: Java Virtual Machines และ Java Applications อดีตหมายถึงการควบคุมขนาดของพาร์ติชันหน่วยความจำตรรกะของเครื่องเสมือนผ่านพารามิเตอร์เครื่องเสมือนจริงตามการออกแบบของแอปพลิเคชันเพื่อให้หน่วยความจำของเครื่องเสมือนจริงเติมเต็มข้อกำหนดของหน่วยความจำของโปรแกรม หลังหมายถึงอัลกอริทึมโปรแกรมที่เหมาะสมลดภาระ GC และปรับปรุงอัตราความสำเร็จของการรีไซเคิล GC
พารามิเตอร์สำหรับการเพิ่มประสิทธิภาพหน่วยความจำเครื่องเสมือนผ่านพารามิเตอร์มีดังนี้:
XMS
ขนาดกองเริ่มต้น
XMX
java heap ค่าสูงสุด
1 ล้าน
กองขนาดเล็กของคนรุ่นใหม่
XSS
ขนาดสแต็กสำหรับแต่ละเธรด
ข้างต้นเป็นพารามิเตอร์ที่ใช้กันทั่วไปอีกสามตัวบางตัว:
xx: minheapfreeratio = 40
เปอร์เซ็นต์ขั้นต่ำของกองฟรีหลังจาก GC เพื่อหลีกเลี่ยงการขยายตัว
XX: MaxHeapFreerAtio = 70
เปอร์เซ็นต์สูงสุดของกองฟรีหลังจาก GC เพื่อหลีกเลี่ยงการหดตัว
XX: newRatio = 2
อัตราส่วนขนาดรุ่นใหม่/เก่า [sparc -client: 8; X86 -Server: 8; x86 -client: 12. ] -ไคลเอนต์: 8 (1.3.1+), x86: 12]
xx: newsize = 2.125m
ขนาดเริ่มต้นของรุ่นใหม่ (ในไบต์) [5.0 และใหม่กว่า: 64 บิต VMs มีขนาดใหญ่ขึ้น 30%; x86: 1m; x86, 5.0 ขึ้นไป: 640k]
xx: maxNewSize =
ขนาดสูงสุดของคนรุ่นใหม่ (ในไบต์) ตั้งแต่ 1.4 MaxNewSize คำนวณเป็นฟังก์ชั่นของ NewRatio
xx: Survivorratio = 25
อัตราส่วนขนาดพื้นที่ Eden/Survivor [SPARC ใน 1.3.1: 25; แพลตฟอร์ม Solaris อื่น ๆ ใน 5.0 และก่อนหน้านี้: 32]
xx: permsize =
ขนาดเริ่มต้นของรุ่นถาวร
xx: maxpermsize = 64m
ขนาดของรุ่นถาวร [5.0 และใหม่กว่า: 64 บิต VMs มีขนาดใหญ่ขึ้น 30%; 1.4 AMD64: 96m; 1.3.1 -client: 32m.]
สิ่งที่กล่าวถึงด้านล่างเพื่อปรับปรุงการใช้หน่วยความจำและลดความเสี่ยงของหน่วยความจำโดยการเพิ่มประสิทธิภาพอัลกอริทึมของโปรแกรมเป็นเชิงประจักษ์ทั้งหมดและมีไว้สำหรับการอ้างอิงเท่านั้น หากมีความไม่เหมาะสมใด ๆ โปรดแก้ไขฉันขอบคุณ!
1. ปล่อยการอ้างอิงของวัตถุที่ไร้ประโยชน์โดยเร็วที่สุด (xx = null;)
ดูรหัสชิ้นหนึ่ง:
รายการสาธารณะ <Pagedata> parse (หน้า htmlPage) {รายการ <pagedata> list = null; ลอง {list valuelist = page.getByxPath (config.getContentxPath ()); if (valuelist == null || valuelist.isempty ()) {return list; } // สร้างวัตถุเมื่อจำเป็นให้บันทึกหน่วยความจำและปรับปรุงรายการประสิทธิภาพ = arrayList ใหม่ <Pagedata> (); pagedata pagedata = ใหม่ pagedata (); stringbuilder value = new StringBuilder (); สำหรับ (int i = 0; i <valuelist.size (); i ++) {htmlelement content = (htmlelement) valuelist.get.get (i); domnodelist <htmlelement> imgs = content.getElementsByTagname ("IMG"); if (imgs! = null &&! imgs.isempty ()) {สำหรับ (htmlelement img: imgs) {ลอง {htmlimage image = (htmlimage) img; String Path = image.getSrCattribute (); string format = path.substring (path.lastindexof ("."), path.length ()); String localPath = "D:/images/" + MD5HELPER.MD5 (เส้นทาง) .retlace ("//", ",") แทนที่ ("/", ",") + รูปแบบ; ไฟล์ localfile = ไฟล์ใหม่ (localpath); if (! localfile.exists ()) {localfile.createnewfile (); image.saveas (localfile); } image.setAttribute ("src", "ไฟล์: ///////-" + localpath); localfile = null; รูปภาพ = null; img = null; } catch (Exception E) {}} // วัตถุนี้จะไม่ถูกใช้ในอนาคต การล้างข้อมูลอ้างอิงนั้นเทียบเท่ากับการบอก GC ล่วงหน้า วัตถุสามารถรีไซเคิล imgs = null; } String text = content.asxml (); value.append (text) .append ("<br/>"); Valuelist = null; เนื้อหา = null; ข้อความ = null; } pagedata.setContent (value.toString ()); pagedata.setcharset (page.getPageencoding ()); list.add (pagedata); // pagedata = null; ไม่มีประโยชน์เพราะรายการยังคงอ้างอิงถึงวัตถุและ GC จะไม่รีไซเคิลค่า = null; // ไม่มีรายการ = null ที่นี่; เนื่องจากรายการคือค่าส่งคืนของวิธีการมิฉะนั้นค่าส่งคืนที่คุณได้รับจากวิธีการจะว่างเปล่าเสมอและข้อผิดพลาดประเภทนี้ไม่ใช่เรื่องง่ายที่จะค้นพบหรือยกเว้น} catch (Exception e) {} รายการส่งคืน; -2. ใช้ประเภทข้อมูลการรวบรวมอย่างระมัดระวังเช่นอาร์เรย์ต้นไม้กราฟรายการที่เชื่อมโยงและโครงสร้างข้อมูลอื่น ๆ โครงสร้างข้อมูลเหล่านี้มีความซับซ้อนมากขึ้นในการรีไซเคิลสำหรับ GC
3. หลีกเลี่ยงการสมัครอย่างชัดเจนสำหรับพื้นที่อาร์เรย์ เมื่อคุณต้องสมัครอย่างชัดเจนลองประเมินมูลค่าที่สมเหตุสมผลให้แม่นยำที่สุดเท่าที่จะทำได้
4. พยายามหลีกเลี่ยงการสร้างและเริ่มต้นวัตถุจำนวนมากในตัวสร้างเริ่มต้นของคลาสและป้องกันการสูญเสียทรัพยากรหน่วยความจำที่ไม่จำเป็นเมื่อเรียกตัวสร้างของตัวเองของคลาส
5. พยายามหลีกเลี่ยงระบบบังคับให้รีไซเคิลหน่วยความจำขยะและเพิ่มเวลาสุดท้ายของการรีไซเคิลขยะในระบบ
6. พยายามใช้ตัวแปรค่าทันทีเมื่อพัฒนาแอปพลิเคชันการโทรแบบระยะไกลเว้นแต่ผู้โทรระยะไกลจะต้องได้รับค่าของตัวแปรค่าทันที
7. พยายามใช้เทคโนโลยีการรวมวัตถุในสถานการณ์ที่เหมาะสมเพื่อปรับปรุงประสิทธิภาพของระบบ