Java Garbage Collection ทำงานร่วมกับเอกสารอ้างอิงแบบวงกลมได้อย่างไร


161

จากความเข้าใจของฉันการเก็บขยะใน Java ล้างวัตถุบางอย่างถ้าไม่มีสิ่งใดที่ 'ชี้' ไปยังวัตถุนั้น

คำถามของฉันคือจะเกิดอะไรขึ้นถ้าเรามีสิ่งนี้:

class Node {
    public object value;
    public Node next;
    public Node(object o, Node n) { value = 0; next = n;}
}

//...some code
{
    Node a = new Node("a", null), 
         b = new Node("b", a), 
         c = new Node("c", b);
    a.next = c;
} //end of scope
//...other code

a, bและcควรเก็บขยะ แต่พวกมันทั้งหมดถูกอ้างอิงโดยวัตถุอื่น

คอลเล็กชันขยะ Java จัดการกับสิ่งนี้อย่างไร (หรือมันเป็นเพียงการระบายความจำ?)


1
ดู: stackoverflow.com/questions/407855/…โดยเฉพาะคำตอบที่สองจาก @gnud
Seth

คำตอบ:


161

GC ของ Java พิจารณาว่า "ขยะ" ของวัตถุหากไม่สามารถเข้าถึงได้ผ่านห่วงโซ่ที่เริ่มต้นที่รูทการรวบรวมขยะดังนั้นวัตถุเหล่านี้จะถูกรวบรวม แม้ว่าวัตถุอาจชี้ให้กันและกันเพื่อก่อให้เกิดวัฏจักรพวกมันยังคงขยะหากถูกตัดขาดจากราก

ดูส่วนของวัตถุที่เข้าไม่ถึงในภาคผนวก A: ความจริงเกี่ยวกับการรวบรวมขยะในประสิทธิภาพของแพลตฟอร์ม Java: กลยุทธ์และยุทธวิธีสำหรับรายละเอียดเต็มไปด้วยเลือด


14
คุณมีการอ้างอิงสำหรับสิ่งนั้นหรือไม่? มันยากที่จะทดสอบ
tangens

5
ฉันเพิ่มการอ้างอิง นอกจากนี้คุณยังสามารถแทนที่เมธอด finalize () ของวัตถุเพื่อดูว่ามันถูกรวบรวมเมื่อใด (แม้ว่าจะเป็นเรื่องเดียว
Bill the Lizard

1
เพียงเพื่อชี้แจงว่าข้อคิดเห็นล่าสุด ... ใส่คำสั่ง debug print ในวิธีการสุดท้ายที่พิมพ์ id เฉพาะสำหรับวัตถุ คุณจะสามารถเห็นวัตถุทั้งหมดที่อ้างอิงซึ่งกันและกันได้รับการรวบรวม
Bill the Lizard

4
"... ฉลาดพอที่จะรับรู้ ... " ฟังดูสับสน GC ไม่ต้องจดจำรอบ - พวกเขาไม่สามารถเข้าถึงได้ดังนั้นขยะ
Alexander Malakhov

86
@tangens "คุณมีการอ้างอิงสำหรับสิ่งนั้นหรือไม่?" ในการอภิปรายเกี่ยวกับการเก็บขยะ ดีที่สุด การเล่นสำนวน เคย.
Michał Kosmulski

139

ใช่ตัวเก็บรวบรวมขยะ Java จัดการกับการอ้างอิงแบบวงกลม!

How?

มีวัตถุพิเศษที่เรียกว่ารูทการรวบรวมขยะ (GC root) สิ่งเหล่านี้สามารถเข้าถึงได้เสมอและเป็นวัตถุใด ๆ ที่มีพวกมันที่รูทของมันเอง

แอปพลิเคชัน Java อย่างง่ายมีราก GC ต่อไปนี้:

  1. ตัวแปรท้องถิ่นในวิธีการหลัก
  2. เธรดหลัก
  3. ตัวแปรสแตติกของคลาสหลัก

ป้อนคำอธิบายรูปภาพที่นี่

เพื่อตรวจสอบว่าวัตถุที่ไม่ได้อยู่ในการใช้งานเป็นระยะ ๆ JVM ทำงานสิ่งที่เรียกว่ามากเหมาะเจาะขั้นตอนวิธีการทำเครื่องหมายและกวาด มันทำงานได้ดังต่อไปนี้

  1. อัลกอริทึมสำรวจการอ้างอิงวัตถุทั้งหมดเริ่มต้นด้วย GC รากและทำเครื่องหมายทุกวัตถุที่พบว่ามีชีวิตอยู่
  2. หน่วยความจำฮีปทั้งหมดที่ไม่ได้ครอบครองโดยวัตถุที่ทำเครื่องหมายไว้จะถูกเรียกคืน มันถูกทำเครื่องหมายอย่างง่าย ๆ ว่าเป็นอิสระกวาดส่วนที่เป็นวัตถุที่ไม่ได้ใช้

ดังนั้นหากวัตถุใด ๆ ไม่สามารถเข้าถึงได้จากราก GC (แม้ว่าจะอ้างอิงด้วยตนเองหรืออ้างอิงแบบวนซ้ำ) วัตถุนั้นจะถูกเก็บรวบรวมขยะ

Ofcourse บางครั้งสิ่งนี้อาจทำให้หน่วยความจำรั่วหากโปรแกรมเมอร์ลืมที่จะตรวจสอบวัตถุ

ป้อนคำอธิบายรูปภาพที่นี่

ที่มา: การจัดการหน่วยความจำ Java


3
คำอธิบายที่สมบูรณ์แบบ! ขอบคุณ! :)
Jovan Perovic

ขอบคุณที่เชื่อมโยงหนังสือเล่มนั้น มันเต็มไปด้วยข้อมูลที่ยอดเยี่ยมเกี่ยวกับเรื่องนี้และหัวข้อการพัฒนา Java อื่น ๆ !
Droj

14
ในภาพสุดท้ายมีวัตถุที่ไม่สามารถเข้าถึงได้ แต่อยู่ในส่วนของวัตถุที่สามารถเข้าถึงได้
La VloZ Merrill

13

ตัวรวบรวมขยะเริ่มต้นจากชุดของ "root" บางตำแหน่งที่ถูกพิจารณาว่า "เข้าถึงได้" เช่นการลงทะเบียน CPU สแต็กและตัวแปรส่วนกลาง มันทำงานได้โดยการค้นหาตัวชี้ใด ๆ ในพื้นที่เหล่านั้นและค้นหาทุกสิ่งที่พวกเขาชี้ซ้ำ เมื่อเจอทุกอย่างแล้วทุกอย่างจะเป็นขยะ

แน่นอนว่ามีรูปแบบไม่กี่แบบส่วนใหญ่เพื่อความรวดเร็ว ยกตัวอย่างเช่นตัวรวบรวมขยะสมัยใหม่ส่วนใหญ่คือ "generational" ซึ่งหมายความว่าพวกเขาแบ่งวัตถุออกเป็นหลายรุ่นและเมื่อวัตถุมีอายุมากขึ้นตัวเก็บขยะก็จะยาวขึ้นเรื่อย ๆ ระหว่างเวลาที่มันพยายามคิดออกว่าวัตถุนั้นยังคงใช้ได้หรือไม่ - มันเริ่มที่จะสมมติว่าหากมันใช้เวลานานโอกาสที่จะค่อนข้างดีที่มันจะดำเนินต่อไปได้อีกต่อไป

อย่างไรก็ตามแนวคิดพื้นฐานยังคงเหมือนเดิม: ทั้งหมดนี้เริ่มต้นจากชุดรากของสิ่งที่ได้รับอนุญาตแล้วยังคงสามารถใช้งานได้จากนั้นไล่ล่าตัวชี้ทั้งหมดเพื่อค้นหาสิ่งที่สามารถใช้งานได้

สิ่งที่น่าสนใจ: ผู้คนมักจะประหลาดใจในระดับความคล้ายคลึงกันระหว่างส่วนนี้ของตัวเก็บขยะและรหัสสำหรับออบเจกต์ marshaling สำหรับสิ่งต่าง ๆ เช่นการเรียกขั้นตอนระยะไกล ในแต่ละกรณีคุณกำลังเริ่มต้นจากชุดของรูทของวัตถุและไล่ตัวชี้เพื่อค้นหาวัตถุอื่น ๆ ทั้งหมดที่อ้างถึง ...


สิ่งที่คุณกำลังอธิบายคือตัวรวบรวมการติดตาม มีนักสะสมประเภทอื่น น่าสนใจโดยเฉพาะสำหรับการอภิปรายครั้งนี้มีการอ้างอิงสะสมนับซึ่งจะมีแนวโน้มที่จะมีปัญหากับรอบ
Jörg W Mittag

@ Jörg W Mittag: จริงแน่นอน - แม้ว่าฉันจะไม่รู้จัก JVM (ปัจจุบันพอสมควร) ที่ใช้การนับการอ้างอิงดังนั้นจึงไม่น่าเป็นไปได้ (อย่างน้อยสำหรับฉัน) ว่ามันสร้างความแตกต่างให้กับคำถามเดิม
Jerry Coffin

@ Jörg W Mittag: อย่างน้อยที่สุดฉันเชื่อว่า Jikes RVM ปัจจุบันใช้ตัวรวบรวม Immix ซึ่งเป็นตัวติดตามการติดตามตามภูมิภาค (แม้ว่าจะใช้การนับการอ้างอิงด้วยก็ตาม) ฉันไม่แน่ใจว่าคุณกำลังอ้างถึงการนับการอ้างอิงนั้นหรือนักสะสมอื่นที่ใช้การนับการอ้างอิงโดยไม่ติดตาม (ฉันเดาหลังเพราะฉันไม่เคยได้ยินว่า Immix เรียก "recycler")
โลงศพเจอร์รี่

ฉันผสมกันเล็กน้อย: Recycler ถูกนำไปใช้ใน Jalapeno อัลกอริทึมที่ฉันคิดซึ่งถูกนำไปใช้ใน Jikes คือการนับจำนวนอ้างอิงแบบคลุมหน้า Atlhough แน่นอนว่า Jikes ใช้สิ่งนี้หรือที่ตัวเก็บขยะค่อนข้างไร้ประโยชน์เนื่องจาก Jikes และโดยเฉพาะอย่างยิ่ง MMtk ได้รับการออกแบบมาเป็นพิเศษเพื่อพัฒนาและทดสอบนักสะสมขยะต่าง ๆ ภายใน JVM เดียวกันอย่างรวดเร็ว
Jörg W Mittag

2
การตรวจนับอ้างอิงแบบซ่อนเร้นได้รับการออกแบบในปี 2003 โดยคนเดียวกันกับผู้ที่ออกแบบ Immix ในปี 2550 ดังนั้นฉันเดาว่าคนหลังอาจแทนที่อดีต URC ได้รับการออกแบบมาโดยเฉพาะเพื่อให้สามารถใช้ร่วมกับกลยุทธ์อื่น ๆ ได้และในความเป็นจริงกระดาษ URC ระบุอย่างชัดเจนว่า URC เป็นเพียงก้าวก้าวสู่นักสะสมที่รวมข้อดีของการติดตามและการนับการอ้างอิง ฉันเดาว่าอิมิกซ์เป็นนักสะสมนั่น อย่างไรก็ตาม Recycler เป็นนักสะสมการอ้างอิงที่บริสุทธิ์ซึ่งยังสามารถตรวจจับและรวบรวมวัฏจักร: WWW.Research.IBM.Com/people/d/dfb/recycler.html
Jörg W Mittag

13

คุณถูก. รูปแบบเฉพาะของการรวบรวมขยะที่คุณอธิบายเรียกว่า " การนับการอ้างอิง " วิธีการใช้งาน (อย่างน้อยที่สุดการใช้งานที่ทันสมัยที่สุดของการนับการอ้างอิงจะถูกนำมาใช้จริงค่อนข้างแตกต่างกัน) ในกรณีที่ง่ายที่สุดมีลักษณะดังนี้:

  • เมื่อใดก็ตามที่มีการเพิ่มการอ้างอิงไปยังวัตถุ (เช่นถูกกำหนดให้กับตัวแปรหรือเขตข้อมูลผ่านไปยังเมธอดและอื่น ๆ ) จำนวนการอ้างอิงจะเพิ่มขึ้น 1
  • เมื่อใดก็ตามที่การอ้างอิงไปยังวัตถุถูกลบ (วิธีการคืนค่าตัวแปรออกนอกขอบเขตฟิลด์จะถูกกำหนดใหม่ให้กับวัตถุอื่นหรือวัตถุที่มีเขตข้อมูลจะได้รับการรวบรวมขยะเอง) จำนวนการอ้างอิงจะลดลง 1
  • ทันทีที่จำนวนการอ้างอิงถึง 0 จะไม่มีการอ้างอิงวัตถุอีกต่อไปซึ่งหมายความว่าไม่มีใครสามารถใช้มันได้อีกต่อไปดังนั้นจึงเป็นขยะและสามารถรวบรวมได้

และกลยุทธ์ง่ายๆนี้มีปัญหาตรงที่คุณบอกเลิก: ถ้า A การอ้างอิง B และ B อ้างอิง A ดังนั้นการนับการอ้างอิงทั้งสองของพวกเขาจะต้องไม่น้อยกว่า 1 ซึ่งหมายความว่าพวกเขาจะไม่ได้รับการเก็บรวบรวม

มีสี่วิธีในการจัดการกับปัญหานี้:

  1. ไม่ต้องสนใจมัน หากคุณมีหน่วยความจำเพียงพอรอบของคุณจะเล็กและไม่บ่อยนักและรันไทม์ของคุณสั้นคุณอาจหนีไปได้โดยไม่ต้องเก็บรอบ ลองนึกถึงตัวแปลเชลล์สคริปต์: โดยปกติแล้วเชลล์สคริปต์จะทำงานเพียงไม่กี่วินาทีและไม่ได้จัดสรรหน่วยความจำมากนัก
  2. รวมการอ้างอิงตัวนับขยะของคุณกับตัวรวบรวมขยะอื่นซึ่งไม่มีปัญหากับรอบ CPython ทำสิ่งนี้เช่น: ตัวรวบรวมขยะหลักใน CPython เป็นตัวรวบรวมการนับการอ้างอิง แต่ในบางครั้งจะมีการเรียกใช้ตัวรวบรวมการติดตามขยะเพื่อรวบรวมรอบ
  3. ตรวจจับรอบ น่าเสียดายที่การตรวจสอบรอบในกราฟเป็นการดำเนินการที่ค่อนข้างแพง โดยเฉพาะอย่างยิ่งมันต้องการโอเวอร์เฮดแบบเดียวกันกับที่ตัวรวบรวมการติดตามต้องการดังนั้นคุณสามารถใช้หนึ่งในนั้นได้เช่นกัน
  4. อย่าใช้อัลกอริทึมอย่างไร้เดียงสากับคุณและฉัน: ตั้งแต่ปี 1970 มีอัลกอริทึมที่น่าสนใจหลายอย่างที่พัฒนาขึ้นซึ่งรวมการตรวจจับวัฏจักรและการอ้างอิงการอ้างอิงในการดำเนินการครั้งเดียวด้วยวิธีที่ชาญฉลาด ทั้งแยกออกจากกันหรือทำการติดตามการสะสม

โดยวิธีการอื่น ๆทางที่สำคัญในการดำเนินการเก็บขยะ (และฉันได้บอกใบ้แล้วที่ว่าสองสามครั้งดังกล่าวข้างต้น) เป็นการติดตาม สะสมติดตามจะขึ้นอยู่กับแนวคิดของการเชื่อม คุณเริ่มต้นด้วยชุดของรูทที่คุณรู้ว่าสามารถเข้าถึงได้เสมอ (ค่าคงที่ทั่วโลกตัวอย่างเช่นหรือObjectคลาสขอบเขตคำศัพท์ปัจจุบันกรอบสแต็กปัจจุบัน) และจากนั้นคุณติดตามวัตถุทั้งหมดที่สามารถเข้าถึงได้จากชุดรากแล้ว วัตถุทั้งหมดที่สามารถเข้าถึงได้จากวัตถุที่สามารถเข้าถึงได้จากชุดรากและอื่น ๆ จนกว่าคุณจะมีการปิดสกรรมกริยา ทุกสิ่งที่ไม่ได้อยู่ในการปิดนั้นคือขยะ

เนื่องจากรอบสามารถเข้าถึงได้ภายในตัวเองเท่านั้น แต่ไม่สามารถเข้าถึงได้จากชุดรูตจึงจะถูกรวบรวม


1
เนื่องจากคำถามเป็นเฉพาะ Java ฉันคิดว่ามันมีค่าควรแก่การกล่าวถึงว่า Java ไม่ได้ใช้การนับการอ้างอิงและด้วยเหตุนี้จึงไม่มีปัญหา นอกจากนี้ยังเชื่อมโยงไปยังวิกิพีเดียจะเป็นประโยชน์ขณะที่ "อ่านต่อไป" ภาพรวมที่ดีเป็นอย่างอื่น!
Alexander Malakhov

ฉันเพิ่งอ่านความคิดเห็นในโพสต์เจอร์รี่โลงศพของดังนั้นตอนนี้ผมไม่แน่ใจว่าที่ :)
อเล็กซานเด Malakhov

8

Java GCs ไม่ทำงานตามที่คุณอธิบาย แม่นยำกว่าที่จะกล่าวว่าพวกเขาเริ่มต้นจากชุดฐานของวัตถุที่เรียกว่า "ราก GC" และจะรวบรวมวัตถุใด ๆ ที่ไม่สามารถเข้าถึงได้จากราก
รากของ GC ประกอบไปด้วย:

  • ตัวแปรสแตติก
  • ตัวแปรโลคัล (รวมถึงการอ้างอิง 'this' ทั้งหมดที่เกี่ยวข้อง) ในปัจจุบันในสแต็กของเธรดที่กำลังรัน

ดังนั้นในกรณีของคุณเมื่อตัวแปรโลคอล a, b และ c อยู่นอกขอบเขตที่จุดสิ้นสุดของวิธีการของคุณจะไม่มีราก GC เพิ่มเติมที่มีทั้งโดยตรงหรือโดยอ้อมอ้างอิงถึงโหนดสามโหนดใด ๆ ของคุณและ พวกเขาจะมีสิทธิ์ได้รับการเก็บขยะ

ลิงค์ของ TofuBeer มีรายละเอียดเพิ่มเติมถ้าคุณต้องการ


"... ขณะนี้อยู่ในสแต็กของเธรดที่กำลังรัน ... " ไม่สแกนสแต็กของเธรดทั้งหมดเพื่อไม่ให้ข้อมูลของเธรดอื่นเสียหายหรือไม่
Alexander Malakhov

6

บทความนี้ (ไม่สามารถใช้งานได้อีกต่อไป) จะเจาะลึกเกี่ยวกับตัวรวบรวมขยะ (ตามแนวคิด ... มีการใช้งานหลายอย่าง) ส่วนที่เกี่ยวข้องกับโพสต์ของคุณคือ "A.3.4 ไม่สามารถเข้าถึงได้":

A.3.4 ไม่สามารถเข้าถึงวัตถุได้เข้าสู่สถานะที่ไม่สามารถเข้าถึงได้เมื่อไม่มีการอ้างอิงที่แข็งแกร่งอีกต่อไป เมื่อวัตถุไม่สามารถเข้าถึงได้มันเป็นตัวเลือกสำหรับการรวบรวม หมายเหตุถ้อยคำ: เพียงเพราะวัตถุเป็นตัวเลือกสำหรับการรวบรวมไม่ได้หมายความว่ามันจะถูกรวบรวมทันที JVM มีอิสระในการชะลอการรวบรวมจนกว่าจะมีความต้องการในทันทีสำหรับหน่วยความจำที่ถูกใช้โดยวัตถุ


1
ลิงก์โดยตรงไปยังส่วนนั้น
Alexander Malakhov

1
ลิงก์ไม่สามารถใช้ได้อีกต่อไป
titus

1

การรวบรวมขยะมักไม่ได้หมายความว่า "ทำความสะอาดวัตถุบางอย่างถ้าไม่มีสิ่งอื่นใดที่" ชี้ "ไปยังวัตถุนั้น" (เป็นการนับการอ้างอิง) การรวบรวมขยะหมายถึงการค้นหาวัตถุที่ไม่สามารถเข้าถึงได้จากโปรแกรม

ดังนั้นในตัวอย่างของคุณหลังจาก a, b และ c พ้นขอบเขตพวกเขาสามารถรวบรวมโดย GC เนื่องจากคุณไม่สามารถเข้าถึงวัตถุเหล่านี้ได้อีกต่อไป


"การรวบรวมขยะหมายถึงการค้นหาวัตถุที่ไม่สามารถเข้าถึงได้จากโปรแกรม" ในอัลกอริทึม GC ส่วนใหญ่มันเป็นวิธีอื่น ๆ คุณเริ่มต้นด้วยราก GC และดูสิ่งที่คุณสามารถหาได้ส่วนที่เหลือถือว่าเป็นขยะที่ไม่มีการอ้างอิง
Fredrik

1
การนับการอ้างอิงเป็นหนึ่งในสองกลยุทธ์หลักของการนำไปปฏิบัติในการรวบรวมขยะ (อีกอย่างคือการติดตาม)
Jörg W Mittag

3
@ Jörg: ส่วนใหญ่แล้ววันนี้เมื่อมีคนพูดถึงนักสะสมขยะพวกเขาจะอ้างถึงนักสะสมตามอัลกอริธึมการทำเครื่องหมายบางอย่างของ mark'n โดยทั่วไปการนับการอ้างอิงเป็นสิ่งที่คุณติดอยู่ถ้าคุณไม่มีตัวเก็บขยะ มันเป็นความจริงที่การนับการอ้างอิงนั้นเป็นกลยุทธ์การเก็บขยะ แต่แทบจะไม่มี gc ที่มีอยู่ในปัจจุบันที่สร้างขึ้นมาเพื่อบอกว่ามันเป็นกลยุทธ์ gc ที่จะทำให้ผู้คนสับสนเพราะในทางปฏิบัติแล้วมันไม่ใช่ gc อีกต่อไป กลยุทธ์ แต่ทางเลือกในการจัดการหน่วยความจำ
Fredrik

1

Bill ตอบคำถามของคุณโดยตรง ดังที่อัมโนนกล่าวว่าคำจำกัดความของการเก็บขยะของคุณเป็นเพียงการอ้างอิง ฉันแค่อยากจะเพิ่มว่าแม้แต่อัลกอริธึมที่ง่ายมากเช่นการทำเครื่องหมายและการกวาดและการเก็บสำเนาก็สามารถจัดการการอ้างอิงแบบวงกลมได้อย่างง่ายดาย ดังนั้นไม่มีอะไรมหัศจรรย์เกี่ยวกับมัน!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.