1. โมเดลหน่วยความจำ Java
เมื่อดำเนินการโปรแกรม Java Virtual Machine จะแบ่งหน่วยความจำที่จัดการออกเป็นหลายพื้นที่ การกระจายของพื้นที่ข้อมูลเหล่านี้แสดงในรูปด้านล่าง:
ตัวนับโปรแกรม: พื้นที่หน่วยความจำขนาดเล็กชี้ไปที่รหัสไบต์ที่ดำเนินการในปัจจุบัน หากเธรดกำลังดำเนินการวิธี Java ตัวนับนี้จะบันทึกที่อยู่ของคำสั่ง Bytecode Virtual Machine ที่กำลังดำเนินการ หากวิธีการดั้งเดิมถูกดำเนินการค่าเครื่องคิดเลขจะว่างเปล่า
Java Virtual Machine Stack: เธรดเป็นส่วนตัวและวงจรชีวิตของพวกเขาสอดคล้องกับเธรด เมื่อมีการดำเนินการแต่ละวิธีเฟรมสแต็กจะถูกสร้างขึ้นเพื่อจัดเก็บข้อมูลเช่นตารางตัวแปรท้องถิ่นตัวถูกดำเนินการสแต็กลิงก์แบบไดนามิกเมธอดทางออก ฯลฯ
Local Method Stack: ฟังก์ชั่นนั้นคล้ายกับสแต็กเครื่องเสมือนยกเว้นว่าสแต็กเครื่องเสมือนดำเนินการบริการ Java Method สำหรับเครื่องเสมือนในขณะที่สแต็กวิธีการท้องถิ่นให้บริการวิธีการดั้งเดิมที่ใช้
Java Heap: มันเป็นหน่วยความจำการจัดการเครื่องเสมือนที่ใหญ่ที่สุดซึ่งแบ่งปันโดยเธรดทั้งหมดพื้นที่นี้ใช้ในการจัดเก็บอินสแตนซ์วัตถุและวัตถุเกือบทั้งหมดจะถูกจัดสรรในพื้นที่นี้ กอง Java เป็นพื้นที่หลักของการรีไซเคิลหน่วยความจำ จากมุมมองของการรีไซเคิลหน่วยความจำเนื่องจากนักสะสมปัจจุบันส่วนใหญ่ใช้อัลกอริธึมการรวบรวม Generational, Java Heap สามารถแบ่งออกเป็น: รุ่นใหม่และรุ่นเก่า หากมีการแบ่งย่อยเล็กน้อยมันสามารถแบ่งออกเป็นพื้นที่อีเด็นจากพื้นที่ผู้รอดชีวิตไปจนถึงพื้นที่รอดชีวิต ฯลฯ ตามข้อกำหนดของเครื่องเสมือน Java เสมือนจริง Java จะอยู่ในพื้นที่ที่ไม่ต่อเนื่องทางร่างกายตราบใดที่มันต่อเนื่องอย่างสมเหตุสมผล
พื้นที่วิธีการ: เช่น Java มันถูกใช้ร่วมกันโดยเธรดต่าง ๆ และใช้ในการจัดเก็บข้อมูลเช่นข้อมูลคลาสที่โหลดโดยเครื่องเสมือนอยู่เสมอตัวแปรคงที่รหัสที่รวบรวมโดยคอมไพเลอร์ทันที
พูลคงที่รันไทม์สระว่ายน้ำคงที่รันไทม์เป็นส่วนหนึ่งของพื้นที่วิธีการ นอกเหนือจากรุ่นคลาสฟิลด์วิธีการอินเทอร์เฟซและข้อมูลคำอธิบายอื่น ๆ ยังมีกลุ่มคงที่ในไฟล์คลาสซึ่งใช้ในการจัดเก็บข้อมูลอ้างอิงที่แท้จริงและสัญลักษณ์ที่สร้างขึ้นในช่วงระยะเวลาการรวบรวม ในระหว่างการรันไทม์ค่าคงที่ใหม่สามารถวางลงในสระคงที่ วิธีที่ใช้กันมากที่สุดคือวิธีการฝึกงาน () ของคลาสสตริง เมื่ออินสแตนซ์สตริงเรียกว่าจาวาพบว่ามีค่าคงที่สตริง Unicode เดียวกันในพูลคงที่หรือไม่ หากมีมันจะส่งคืนการอ้างอิง; ถ้าไม่เพิ่ม Unicode เท่ากับสตริงอินสแตนซ์และส่งคืนการอ้างอิง
2. วิธีการกำหนดวัตถุขยะ
มีวัตถุหลายอย่างที่เก็บไว้ในกอง Java ก่อนที่นักสะสมขยะจะรีไซเคิลกองจะต้องพิจารณาว่าวัตถุใดที่ยังคง "มีชีวิตอยู่" และมี "ตาย" นั่นคือวัตถุที่จะไม่ถูกใช้โดยวิธีการใด ๆ
การนับจำนวน
วิธีการนับการอ้างอิงนั้นง่ายต่อการใช้งานและมีประสิทธิภาพและเป็นอัลกอริทึมที่ดีในกรณีส่วนใหญ่ หลักการคือ: เพิ่มตัวนับอ้างอิงไปยังวัตถุ เมื่อใดก็ตามที่มีสถานที่อ้างอิงวัตถุตัวนับจะเพิ่มขึ้น 1 เมื่อการอ้างอิงล้มเหลวตัวนับจะลดลง 1 เมื่อค่าตัวนับคือ 0 หมายความว่าวัตถุไม่ได้ใช้อีกต่อไป ควรสังเกตว่าวิธีการนับการอ้างอิงนั้นยากที่จะแก้ปัญหาการอ้างอิงร่วมกันระหว่างวัตถุและเครื่องเสมือน Java หลักไม่ได้ใช้วิธีการนับอ้างอิงเพื่อจัดการหน่วยความจำ
อัลกอริทึมการวิเคราะห์การเข้าถึง
แนวคิดพื้นฐานของอัลกอริทึมนี้คือการค้นหาลงผ่านชุดของวัตถุที่เรียกว่า "GC Roots" เป็นจุดเริ่มต้นเริ่มต้นจากโหนดเหล่านี้ เส้นทางที่ค้นหาผ่านเรียกว่าห่วงโซ่อ้างอิง เมื่อวัตถุไม่ได้เชื่อมต่อกับราก GC โดยไม่มีห่วงโซ่อ้างอิงใด ๆ (ในคำพูดของทฤษฎีกราฟมันมาจากราก GC ไปยังวัตถุนี้ที่ไม่สามารถเข้าถึงได้) มันจะพิสูจน์ได้ว่าวัตถุนี้ไม่สามารถใช้งานได้ ดังที่แสดงในรูปแม้ว่าวัตถุ 5 วัตถุ 6 และ Object 7 นั้นเกี่ยวข้องกัน
ในภาษา Java วัตถุต่อไปนี้ที่สามารถใช้เป็นราก GC รวมถึง:
วัตถุที่อ้างอิงในสแต็กเครื่องเสมือน (ตารางตัวแปรท้องถิ่นในเฟรมสแต็ก)
วัตถุที่อ้างอิงโดยแอตทริบิวต์คงที่ของคลาสในพื้นที่เมธอด
วัตถุที่อ้างอิงโดยค่าคงที่ในพื้นที่วิธีการ
วัตถุที่อ้างอิงโดย JNI (นั่นคือวิธีการทั่วไปทั่วไป) ในสแต็กวิธีการท้องถิ่น
ตอนนี้คำถามคืออัลกอริทึมการวิเคราะห์การเข้าถึงจะมีปัญหาการอ้างอิงแบบวงกลมระหว่างวัตถุหรือไม่? คำตอบคือใช่นั่นคือจะไม่มีปัญหาในการอ้างอิงแบบวงกลมระหว่างวัตถุ GC Root เป็น "จุดเริ่มต้น" ที่กำหนดไว้เป็นพิเศษนอกกราฟวัตถุและไม่สามารถอ้างอิงได้โดยวัตถุในกราฟวัตถุ
ตายหรือไม่ตาย
แม้แต่วัตถุที่ไม่สามารถเข้าถึงได้ในอัลกอริทึมการวิเคราะห์การเข้าถึงก็ไม่ได้ "ต้องตาย" ในเวลานี้พวกเขาอยู่ในขั้นตอน "การคุมประพฤติ" ชั่วคราว ในการประกาศวัตถุที่ตายไปอย่างแท้จริงมันจะต้องผ่านกระบวนการทำเครื่องหมายอย่างน้อยสองกระบวนการ: หากวัตถุพบว่าไม่มีห่วงโซ่อ้างอิงที่เชื่อมต่อกับราก GC หลังจากทำการวิเคราะห์การเข้าถึงการเข้าถึงมันจะถูกทำเครื่องหมายเป็นครั้งแรกและกรอง เงื่อนไขการกรองคือไม่ว่าจะเป็นสิ่งจำเป็นสำหรับวัตถุนี้ในการดำเนินการวิธี FINAPZE () เมื่อวัตถุไม่ได้เขียนทับวิธี FINAPZE () หรือวิธี FINAPZE () ได้รับการเรียกโดยเครื่องเสมือนเครื่องเสมือนจะถือว่าทั้งสองกรณีเป็น "ไม่จำเป็นต้องดำเนินการ" ในโปรแกรมคุณสามารถเขียนทับ finapze () เพื่อสร้างกระบวนการ "น่าตื่นเต้น" แต่นี่เป็นเพียงโอกาสเดียวเท่านั้น
/** * รหัสนี้แสดงให้เห็นถึงสองจุด: * 1. วัตถุสามารถบันทึกตัวเองได้เมื่อพวกเขาเป็น GC * 2. มีโอกาสเพียงครั้งเดียวของการช่วยเหลือตนเองเนื่องจากวิธี FINAPZE () ของวัตถุจะถูกเรียกโดยอัตโนมัติเพียงครั้งเดียวโดยระบบส่วนใหญ่ * @author ZZM */ PUBPC คลาส FinapzeesCapegc {pubpc คงที่ finapzeescapegc save_hook = null; pubpc void isapve () {system.out.println ("ใช่ฉันยังคงเป็น apve :)"); } @Override void void finapze () พ่น throws {super.finapze (); System.out.println ("Finapze Mehtod ดำเนินการ!"); finapzeescapegc.save_hook = this; } pubpc โมฆะคงที่หลัก (สตริง [] args) พ่น throwable {save_hook = ใหม่ finapzeescapegc (); // วัตถุนั้นบันทึกตัวเองได้สำเร็จเป็นครั้งแรก save_hook = null; System.gc (); // เนื่องจากวิธี FINAPZE มีลำดับความสำคัญต่ำหยุดชั่วคราวเป็นเวลา 0.5 วินาทีเพื่อรอ Thread.sleep (500); if (save_hook! = null) {save_hook.isapve (); } else {system.out.println ("ไม่ฉันตายแล้ว :(");} // รหัสต่อไปนี้เหมือนกันกับข้างต้น แต่คราวนี้การช่วยเหลือตนเองล้มเหลว save_hook = null; system.gc (); // เพราะวิธี finapze มีความสำคัญต่ำ save_hook.isapve ();} else {system.out.println ("ไม่ฉันตายแล้ว :(");ผลการทำงานคือ:
Finapze Mehtod ดำเนินการ! ใช่ฉันยังคงเป็น apve :) ไม่ฉันตายแล้ว :(
พูดคุยเกี่ยวกับการอ้างอิง
ไม่ว่าจะเป็นการตัดสินจำนวนการอ้างอิงของวัตถุผ่านอัลกอริทึมการนับอ้างอิงหรือพิจารณาว่าห่วงโซ่อ้างอิงของวัตถุนั้นสามารถเข้าถึงได้ผ่านอัลกอริทึมการวิเคราะห์การเข้าถึงหรือไม่โดยพิจารณาว่าการอยู่รอดของวัตถุนั้นเกี่ยวข้องกับ "การอ้างอิง" ก่อน JDK 1.2 คำจำกัดความของการอ้างอิงใน Java นั้นเป็นแบบดั้งเดิมมาก: หากค่าที่เก็บไว้ในข้อมูลประเภทอ้างอิงแสดงถึงที่อยู่เริ่มต้นของหน่วยความจำอีกชิ้นหนึ่งก็มีการกล่าวกันว่าหน่วยความจำชิ้นนี้แสดงถึงการอ้างอิง หลังจาก JDK 1.2 แล้ว Java ได้ขยายแนวคิดของการอ้างอิงและแบ่งการอ้างอิงออกเป็นสี่ประเภท: การอ้างอิงที่แข็งแกร่งการอ้างอิงที่อ่อนนุ่มการอ้างอิงที่อ่อนแอและการอ้างอิง Phantom ความแข็งแกร่งของการอ้างอิงทั้งสี่ประเภทนี้ค่อยๆลดลงอย่างค่อยเป็นค่อยไปในทางกลับกัน
•การอ้างอิงที่แข็งแกร่งหมายถึงการอ้างอิงที่พบได้ทั่วไปในรหัสโปรแกรมเช่น "Object OBJ = New Object ()" ตราบใดที่การอ้างอิงที่แข็งแกร่งยังคงมีอยู่นักสะสมขยะจะไม่รีไซเคิลวัตถุที่อ้างอิง
•การอ้างอิงที่อ่อนนุ่มใช้เพื่ออธิบายวัตถุที่มีประโยชน์ แต่ไม่จำเป็น สำหรับวัตถุที่เกี่ยวข้องกับการอ้างอิงแบบนุ่ม ๆ วัตถุเหล่านี้จะถูกระบุไว้ในขอบเขตการรีไซเคิลสำหรับการรีไซเคิลครั้งที่สองก่อนที่ระบบจะมีข้อยกเว้นการไหลล้นหน่วยความจำ หากมีหน่วยความจำไม่เพียงพอสำหรับการรีไซเคิลนี้จะมีการโยนข้อยกเว้นของหน่วยความจำล้น หลังจาก JDK 1.2 คลาส Softreference จะถูกจัดเตรียมไว้เพื่อใช้การอ้างอิงที่อ่อนนุ่ม
•การอ้างอิงที่อ่อนแอยังใช้เพื่ออธิบายวัตถุที่ไม่จำเป็น แต่ความแข็งแรงของพวกเขาอ่อนแอกว่าการอ้างอิงที่อ่อนนุ่ม วัตถุที่เกี่ยวข้องกับการอ้างอิงที่อ่อนแอสามารถอยู่รอดได้จนกว่าจะมีการรวบรวมขยะครั้งต่อไป เมื่อตัวรวบรวมขยะทำงานวัตถุที่เกี่ยวข้องกับการอ้างอิงที่อ่อนแอจะถูกรวบรวมโดยไม่คำนึงว่าหน่วยความจำปัจจุบันเพียงพอหรือไม่ หลังจาก JDK 1.2 คลาสที่อ่อนแอจะถูกจัดเตรียมไว้เพื่อใช้การอ้างอิงที่อ่อนแอ
•คำพูดที่เป็นโมฆะเรียกอีกอย่างว่าคำคมผีหรือคำพูด Phantom และพวกเขาเป็นความสัมพันธ์ที่อ่อนแอที่สุด ไม่ว่าวัตถุจะมีการอ้างอิงเสมือนจริงจะไม่มีผลกระทบใด ๆ ต่อเวลาการอยู่รอดเลยหรือไม่และจะไม่สามารถรับอินสแตนซ์ของวัตถุผ่านการอ้างอิงเสมือนจริง จุดประสงค์เดียวในการตั้งค่าการเชื่อมโยงการอ้างอิงเสมือนสำหรับวัตถุคือการรับการแจ้งเตือนระบบเมื่อวัตถุถูกรีไซเคิลโดยนักสะสม หลังจาก JDK 1.2 คลาส phantomreference มีให้เพื่อการอ้างอิงเสมือนจริง
ตัวอย่างการใช้ข้อมูลอ้างอิงที่อ่อนนุ่ม:
แพ็คเกจ jvm; นำเข้า java.lang.ref.softreference; class node {pubpc string msg = "";} คลาส pubpc สวัสดี {pubpc โมฆะคงที่หลัก (สตริง [] args) {node node1 = new node (); // การอ้างอิงที่แข็งแกร่ง node1.msg = "node1"; softreference <node> node2 = softreference ใหม่ <node> (node1); // ซอฟต์อ้างอิง node2.get (). msg = "node2"; system.out.println (node1.msg); system.out.println (node2.get (). msg);}}ผลลัพธ์ผลลัพธ์คือ:
Node2Node2
3. อัลกอริทึมการรวบรวมขยะทั่วไป
1.Mark-Sweep (MARK-CLEAR) อัลกอริทึม
นี่คืออัลกอริทึมการรวบรวมขยะขั้นพื้นฐานที่สุด เหตุผลที่มีการกล่าวกันว่าเป็นพื้นฐานที่สุดคือมันเป็นวิธีที่ง่ายที่สุดในการใช้งานและความคิดที่ง่ายที่สุด อัลกอริทึมการล้างเครื่องหมายแบ่งออกเป็นสองขั้นตอน: ขั้นตอนการทำเครื่องหมายและขั้นตอนการล้าง ภารกิจของขั้นตอนการทำเครื่องหมายคือการทำเครื่องหมายวัตถุทั้งหมดที่ต้องรีไซเคิลและขั้นตอนการล้างคือการรีไซเคิลพื้นที่ที่ถูกครอบครองโดยวัตถุที่ทำเครื่องหมายไว้ กระบวนการเฉพาะแสดงในรูปด้านล่าง:
มันสามารถมองเห็นได้ง่ายจากรูปที่อัลกอริทึมการล้างเครื่องหมายนั้นง่ายต่อการใช้งาน แต่มีปัญหาร้ายแรงที่ง่ายต่อการสร้างชิ้นส่วนหน่วยความจำ ชิ้นส่วนมากเกินไปอาจทำให้ไม่สามารถหาพื้นที่เพียงพอเมื่อจัดสรรพื้นที่สำหรับวัตถุขนาดใหญ่ในกระบวนการที่ตามมาและกระตุ้นการดำเนินการรวบรวมขยะใหม่ล่วงหน้า
2. อัลกอริทึมการคัดลอก
เพื่อที่จะแก้ปัญหาข้อบกพร่องของอัลกอริทึมมาร์ค-กวาดได้เสนออัลกอริทึมการคัดลอก มันแบ่งหน่วยความจำที่มีอยู่เป็นสองชิ้นที่มีขนาดเท่ากันโดยใช้เพียงชิ้นเดียวในแต่ละครั้ง เมื่อหน่วยความจำชิ้นนี้หมดแล้วให้คัดลอกวัตถุที่ยังมีชีวิตไปยังอีกชิ้นหนึ่งจากนั้นทำความสะอาดพื้นที่หน่วยความจำที่ใช้ในครั้งเดียวเพื่อให้ปัญหาการกระจายตัวของหน่วยความจำจะไม่เกิดขึ้น กระบวนการเฉพาะแสดงในรูปด้านล่าง:
แม้ว่าอัลกอริทึมนี้จะใช้งานได้ง่าย แต่ก็มีประสิทธิภาพในการทำงานและไม่ง่ายที่จะสร้างการกระจายตัวของหน่วยความจำ แต่ก็มีราคาแพงในการใช้พื้นที่หน่วยความจำเนื่องจากหน่วยความจำที่สามารถใช้งานได้ลดลงเหลือครึ่งหนึ่งของต้นฉบับ
เห็นได้ชัดว่าประสิทธิภาพของอัลกอริทึมการคัดลอกมีส่วนเกี่ยวข้องกับจำนวนวัตถุที่รอดชีวิต หากมีวัตถุที่รอดชีวิตมากมายประสิทธิภาพของอัลกอริทึมการคัดลอกจะลดลงอย่างมาก
3. อัลกอริทึม Mark-compact (Mark-collation)
เพื่อแก้ข้อบกพร่องของอัลกอริทึมการคัดลอกและใช้ประโยชน์จากพื้นที่หน่วยความจำอย่างเต็มที่ อัลกอริทึมทำเครื่องหมายเช่นเดียวกับมาร์คกวาด แต่หลังจากทำเครื่องหมายเสร็จแล้วมันจะไม่ทำความสะอาดวัตถุรีไซเคิลโดยตรง แต่ย้ายวัตถุที่มีชีวิตทั้งหมดไปยังปลายด้านหนึ่งแล้วทำความสะอาดหน่วยความจำนอกขอบเขตสิ้นสุด กระบวนการเฉพาะแสดงในรูปด้านล่าง:
4. อัลกอริทึมการรวบรวม Generational
อัลกอริทึมการสร้างการสร้างปัจจุบันถูกใช้โดยนักสะสมขยะ JVM ส่วนใหญ่ แนวคิดหลักของมันคือการแบ่งหน่วยความจำออกเป็นหลายภูมิภาคตามวงจรชีวิตของการอยู่รอดของวัตถุ โดยทั่วไปพื้นที่กองจะแบ่งออกเป็นคนรุ่นเก่าและคนรุ่นใหม่ ลักษณะของรุ่นเก่าคือมีเพียงวัตถุจำนวนน้อยที่ต้องรีไซเคิลทุกครั้งที่เก็บขยะในขณะที่ลักษณะของคนรุ่นใหม่คือวัตถุจำนวนมากต้องรีไซเคิลทุกครั้งที่เก็บขยะ จากนั้นอัลกอริทึมการรวบรวมที่เหมาะสมที่สุดสามารถนำมาใช้ตามลักษณะของรุ่นต่าง ๆ
ในปัจจุบันนักสะสมขยะส่วนใหญ่ใช้อัลกอริทึมการคัดลอกสำหรับคนรุ่นใหม่เพราะในรุ่นใหม่วัตถุส่วนใหญ่จะต้องรีไซเคิลทุกครั้งนั่นคือจำนวนการดำเนินการที่ต้องคัดลอกมีขนาดเล็ก แต่ในความเป็นจริงพื้นที่ของคนรุ่นใหม่ไม่ได้ถูกแบ่งตามอัตราส่วน 1: 1 โดยทั่วไปแล้วคนรุ่นใหม่จะแบ่งออกเป็นพื้นที่อีเด็นขนาดใหญ่และพื้นที่ผู้รอดชีวิตขนาดเล็กสองแห่ง (โดยปกติ 8: 1: 1) ทุกครั้งที่มีการใช้พื้นที่ Eden และหนึ่งในช่องว่างผู้รอดชีวิตเมื่อรีไซเคิลวัตถุที่ยังคงอยู่รอดใน Eden และ Survivor จะถูกคัดลอกไปยังพื้นที่ผู้รอดชีวิตอีกแห่งหนึ่งจากนั้น Eden และ Spaces ผู้รอดชีวิตที่เพิ่งใช้ถูกทำความสะอาด
เนื่องจากวัยชราคือมีเพียงวัตถุจำนวนน้อยเท่านั้นที่ถูกนำกลับมาใช้ใหม่ทุกครั้งจึงใช้อัลกอริทึมที่มีขนาดกะทัดรัด
การวิเคราะห์สั้น ๆ ข้างต้นของโมเดลหน่วยความจำ Java และการรวบรวมขยะเป็นเนื้อหาทั้งหมดที่ฉันแบ่งปันกับคุณ ฉันหวังว่าคุณจะให้ข้อมูลอ้างอิงและฉันหวังว่าคุณจะสนับสนุน wulin.com มากขึ้น