เพราะเหตุใดจึงมีการสรุปขั้นสุดท้ายใน Java?


44

ตามโพสต์นี้เราไม่ควรพึ่งพาวิธีสุดท้ายที่จะเรียกว่า ดังนั้นทำไม Java จึงรวมไว้ในภาษาการเขียนโปรแกรมเลย?

ดูเหมือนว่าการตัดสินใจที่จะรวมไว้ในภาษาการเขียนโปรแกรมใด ๆ ฟังก์ชั่นที่อาจได้รับการเรียก

คำตอบ:


41

ตาม Java ที่มีประสิทธิภาพของ Joshua Bloch (รุ่นที่สอง) มีสองสถานการณ์เมื่อfinalize()เป็นประโยชน์:

  1. หนึ่งคือการทำหน้าที่เป็น "ตาข่ายความปลอดภัย" ในกรณีที่เจ้าของวัตถุลืมที่จะเรียกวิธีการเลิกจ้างที่ชัดเจน ในขณะที่ไม่มีการรับประกันว่า finalizer จะถูกเรียกใช้ทันทีมันอาจจะดีกว่าที่จะปล่อยให้ทรัพยากรช้ากว่าที่ไม่เคยเกิดขึ้นในกรณี (หวังว่าจะหายาก) ในกรณีที่ลูกค้าไม่สามารถเรียกวิธีการยกเลิกอย่างชัดเจน แต่ finalizer ควรบันทึกคำเตือนหากพบว่าทรัพยากรไม่ได้ถูกยกเลิก

  2. การใช้ finalizers อย่างถูกต้องตามกฎหมายครั้งที่สองจะเกี่ยวข้องกับวัตถุกับเพียร์ดั้งเดิม เพียร์พื้นเมืองคือวัตถุพื้นเมืองที่วัตถุปกติมอบหมายผ่านวิธีการเนทิฟ เนื่องจากเพียร์เนทีฟไม่ใช่วัตถุปกติตัวเก็บรวบรวมขยะจึงไม่ทราบและไม่สามารถเรียกคืนได้เมื่อเพียร์ Java ถูกเรียกคืน finalizer เป็นยานพาหนะที่เหมาะสมสำหรับการทำงานนี้โดยสมมติว่าเพียร์ดั้งเดิมไม่มีทรัพยากรที่สำคัญ หากเพียร์เนทีฟมีทรัพยากรที่ต้องยกเลิกในทันทีคลาสควรมีวิธีการยกเลิกอย่างชัดเจนตามที่อธิบายไว้ข้างต้น วิธีการเลิกจ้างควรทำทุกอย่างที่จำเป็นเพื่อให้ทรัพยากรที่สำคัญหลุดพ้น

สำหรับการอ่านเพิ่มเติมให้ดูข้อ 7 หน้า 27


4
ด้วย # 2 ดูเหมือนว่าเนทีฟเพียร์เป็นแหล่งข้อมูลพื้นเมืองที่ต้องมีเครือข่ายความปลอดภัยและควรมีวิธีการเลิกจ้างดังนั้นจึงไม่ควรแยกจากกัน
Mooing Duck

2
@MooingDuck: # 2 เป็นจุดแยกต่างหากเพราะหากเพียร์เนทีฟไม่มีทรัพยากรที่สำคัญ (เช่นเป็นวัตถุในหน่วยความจำอย่างหมดจด) ก็ไม่จำเป็นต้องเปิดเผยวิธีการเลิกจ้างที่ชัดเจน เครือข่ายความปลอดภัยจาก # 1 ชัดเจนเกี่ยวกับการมีวิธีการเลิกจ้าง
jhominal

55

Finalizers มีความสำคัญสำหรับการจัดการทรัพยากรพื้นเมือง ตัวอย่างเช่นวัตถุของคุณอาจต้องจัดสรร WidgetHandle จากระบบปฏิบัติการโดยใช้ที่ไม่ใช่ Java API หากคุณไม่ปล่อย WidgetHandle นั้นเมื่อวัตถุของคุณเป็น GC'd คุณจะต้องปล่อย WidgetHandles ออกมา

สิ่งสำคัญคือกรณี "finalizer ไม่เคยถูกเรียกว่า" แยกย่อยได้ง่าย ๆ :

  1. โปรแกรมจะปิดตัวลงอย่างรวดเร็ว
  2. วัตถุ "อยู่ตลอดไป" ในช่วงอายุการใช้งานของโปรแกรม
  3. คอมพิวเตอร์ปิด / กระบวนการของคุณถูกฆ่าโดย OS / etc

ในทั้งสามกรณีนี้คุณอาจไม่ได้มีการรั่วไหลของพื้นเมือง (โดยอาศัยอำนาจตามความจริงที่ว่าโปรแกรมที่คุณไม่ได้ทำงานอีกต่อไป) หรือคุณมีการรั่วไหลที่ไม่ใช่เจ้าของภาษา (ถ้าคุณให้จัดสรรวัตถุที่มีการจัดการโดยที่พวกเขาเป็น GC'd)

คำเตือน "ไม่พึ่งพา finalizer ที่เรียกว่า" เป็นเรื่องเกี่ยวกับการไม่ใช้ finalizers สำหรับตรรกะของโปรแกรม ตัวอย่างเช่นคุณไม่ต้องการติดตามจำนวนวัตถุของคุณที่มีอยู่ในทุกอินสแตนซ์ของโปรแกรมของคุณโดยการเพิ่มตัวนับในไฟล์ที่ใดที่หนึ่งระหว่างการก่อสร้าง สรุปแล้วตัวนับไฟล์นี้อาจจะไม่กลับไปเป็น 0 นี่เป็นกรณีพิเศษของหลักการทั่วไปที่คุณไม่ควรพึ่งพาโปรแกรมของคุณที่จะยุติการทำงานตามปกติ (ไฟฟ้าขัดข้อง ฯลฯ )

สำหรับการจัดการทรัพยากรดั้งเดิมนั้นกรณีที่ตัวเรียกใช้ขั้นสุดท้ายไม่ทำงานสอดคล้องกับกรณีที่คุณไม่สนใจหากไม่ได้ทำงาน


คำตอบที่ยอดเยี่ยม ฉันชอบแม้ว่ามันอาจจะถูกเรียกว่า .. มันก็ควรจะรวมอยู่ด้วย ฉันสับสนในขณะที่คำถามและเชื่อมโยงใน StackOverflow
แจ้งเมื่อ

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

26
"Finalizers มีความสำคัญสำหรับการจัดการทรัพยากรพื้นเมือง" - nooooooooo, อะแฮ่มขอโทษ การใช้ finalizers สำหรับการจัดการทรัพยากรพื้นเมืองเป็นความคิดที่น่ากลัว (ซึ่งเป็นเหตุผลว่าทำไมเราถึงมีการ autocloseable GC คำนึงถึงหน่วยความจำเท่านั้นไม่ใช่ทรัพยากรอื่น ๆ ซึ่งหมายความว่าคุณสามารถใช้ทรัพยากรในท้องถิ่นได้หมด แต่ยังมีหน่วยความจำเพียงพอที่จะไม่เรียกใช้ GC - เราไม่ต้องการ ผู้เข้ารอบสุดท้ายทำให้ GC มีราคาแพงขึ้นซึ่งไม่ใช่ความคิดที่ดีเช่นกัน
Voo

12
ผมเห็นคุณยังควรมีความชัดเจนกลยุทธ์การจัดการทรัพยากรพื้นเมืองอื่น ๆ กว่า finalizers แต่ถ้าวัตถุของคุณไม่จัดสรรพวกเขาและไม่ฟรีพวกเขาใน finalizer คุณกำลังเปิดตัวเองถึงการรั่วไหลของพื้นเมืองในกรณีที่วัตถุนั้น GC'd โดยไม่มีการเพิ่มทรัพยากรอย่างชัดเจน กล่าวอีกนัยหนึ่ง finalizers เป็นส่วนสำคัญของกลยุทธ์การจัดการทรัพยากรท้องถิ่นไม่ใช่วิธีแก้ปัญหาโดยทั่วไป
Ryan Cavanaugh

1
@Voo มันอาจจะถูกต้องมากกว่าที่จะพูด finalizer เป็นทางเลือกในกรณีที่สัญญาของวัตถุที่ไม่ปฏิบัติตาม ( close()จะไม่เรียกว่าก่อนที่มันจะกลายเป็นไม่สามารถเข้าถึง)
วงล้อประหลาด

5

วัตถุประสงค์ของวิธีนี้อธิบายไว้ในเอกสารประกอบ APIดังต่อไปนี้:

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

วัตถุประสงค์ปกติของfinalize... คือการดำเนินการก่อนที่จะทำความสะอาดวัตถุที่ถูกทิ้งอย่างถาวร ตัวอย่างเช่นวิธีการสุดท้ายสำหรับวัตถุที่แสดงถึงการเชื่อมต่ออินพุต / เอาท์พุตอาจทำธุรกรรม I / O ที่ชัดเจนเพื่อทำลายการเชื่อมต่อก่อนที่วัตถุจะถูกทิ้งอย่างถาวร ...


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

การรวบรวมขยะอัตโนมัติ ... กำจัดคลาสทั้งหมดของข้อผิดพลาดในการเขียนโปรแกรมที่ตัวควบคุม C และ C ++ คุณสามารถพัฒนาโค้ด Java ด้วยความมั่นใจว่าระบบจะพบข้อผิดพลาดมากมายได้อย่างรวดเร็วและปัญหาสำคัญจะไม่หยุดนิ่งจนกว่าจะส่งรหัสการผลิตของคุณ

ในทางกลับกันอ้างจากเอกสารทางราชการเกี่ยวกับเป้าหมายการออกแบบของ Javaนั่นคือการอ้างอิงที่เชื่อถือได้อธิบายว่าทำไมนักออกแบบภาษา Java จึงตัดสินใจด้วยวิธีนี้

สำหรับรายละเอียดเพิ่มเติมและการสนทนาภาษาผู้ไม่เชื่อเรื่องพระเจ้าของการตั้งค่านี้ดูOOSCมาตรา9.6 การจัดการหน่วยความจำอัตโนมัติ (อันที่จริงไม่เพียง แต่ส่วนนี้ แต่บทที่ 9 ทั้งหมดมีมูลค่าการอ่านถ้าคุณสนใจในสิ่งนั้น) ส่วนนี้จะเปิดขึ้นพร้อมกับคำสั่งที่ชัดเจน:

สภาพแวดล้อม OO ที่ดีควรมีกลไกการจัดการหน่วยความจำอัตโนมัติซึ่งจะตรวจจับและเรียกคืนออบเจ็กต์ที่ไม่สามารถเข้าถึงได้ช่วยให้นักพัฒนาแอปพลิเคชันมีสมาธิกับงาน - การพัฒนาแอปพลิเคชัน

การสนทนาก่อนหน้านี้ควรพอเพียงเพื่อแสดงให้เห็นว่าการให้ความสะดวกนั้นมีความสำคัญเช่นไร ในคำพูดของ Michael Schweitzer และ Lambert Strether:

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


4

Finalizers มีอยู่เพราะพวกเขาคาดว่าจะเป็นวิธีที่มีประสิทธิภาพในการทำให้แน่ใจว่าสิ่งต่าง ๆ ได้รับการทำความสะอาด (แม้ว่าในทางปฏิบัติแล้วพวกเขาไม่ได้) และเพราะเมื่อพวกเขาถูกประดิษฐ์ขึ้นมา - แหล่งข้อมูล) ยังไม่มี ในความเข้าใจย้อนหลัง Java น่าจะดีกว่าหากความพยายามที่ใช้ในการดำเนินการสิ่งอำนวยความสะดวก "เสร็จสิ้น" ได้ถูกใช้ไปกับวิธีการล้างอื่น ๆ แต่ก็ไม่ค่อยชัดเจนในช่วงแรกที่ Java ถูกพัฒนาขึ้น


3

ถ้ำ: ฉันอาจจะล้าสมัย แต่นี่เป็นความเข้าใจของฉันเมื่อไม่กี่ปีที่ผ่านมา:

โดยทั่วไปจะไม่มีการรับประกันว่าเมื่อใดที่ตัวเลือกสุดท้ายทำงาน - หรือแม้กระทั่งที่ตัวเรียกใช้เลยแม้ว่า JVM บางตัวจะช่วยให้คุณร้องขอ GC และการสรุปที่สมบูรณ์ก่อนที่โปรแกรมจะออก (ซึ่งแน่นอนว่าโปรแกรมใช้เวลานานขึ้น เพื่อออกและไม่ใช่โหมดการทำงานเริ่มต้น)

และบางคนรู้ว่า GCs ล่าช้าอย่างชัดเจนหรือหลีกเลี่ยงวัตถุ GC'ing ที่มี finalizers ด้วยความหวังว่าสิ่งนี้จะสร้างประสิทธิภาพที่ดีขึ้นในการวัดประสิทธิภาพ

น่าเสียดายที่พฤติกรรมเหล่านี้ขัดแย้งกับเหตุผลดั้งเดิมที่แนะนำให้ผู้เข้ารอบสุดท้ายและสนับสนุนให้ใช้วิธีการปิดระบบที่เรียกอย่างชัดเจนแทน

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


1

คู่มือสไตล์ Google Javaมีคำแนะนำปัญญาชนบางอย่างเกี่ยวกับเรื่อง:

มันเป็นเรื่องยากมากObject.finalizeที่จะแทนที่

เคล็ดลับ : อย่าทำ ถ้าคุณอย่างจะต้องเป็นครั้งแรกที่อ่านและทำความเข้าใจที่มีประสิทธิภาพ Javaรายการที่ 7 "Finalizers หลีกเลี่ยง" อย่างระมัดระวังและจากนั้นไม่ได้ทำมัน


5
ทำไมฉันถึงชื่นชมการใช้งานเกินขนาดของปัญหา "อย่าทำ" ไม่ใช่คำตอบสำหรับ "ทำไมถึงมีอยู่"
Pierre Arlaud

1
ฉันเดาว่าฉันไม่ได้สะกดคำตอบออกมาได้ดี แต่ (IMO) คำตอบของ "ทำไมจึงมีอยู่" คือ "ไม่ควร" สำหรับกรณีที่หายากเหล่านั้นซึ่งคุณต้องการดำเนินการขั้นสุดท้ายคุณอาจต้องการสร้างด้วยตัวเองด้วยPhantomReferenceและReferenceQueueแทนที่
Daniel Pryden

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

1
แม้แต่ในกรณีของคนพื้นเมืองฉันคิดว่าPhantomReferenceมันเป็นทางออกที่ดีกว่า Finalizers เป็นหูดที่เหลือจากวันแรกของ Java และชอบObject.clone()และประเภทดิบเป็นส่วนหนึ่งของภาษาที่ดีที่สุดที่ถูกลืม
Daniel Pryden

1
อันตรายของ finalizers ถูกอธิบายในลักษณะที่เข้าถึงได้ง่ายขึ้น (เข้าใจได้โดยผู้พัฒนา) ใน C # แม้ว่าเราจะต้องไม่บอกเป็นนัยว่ากลไกพื้นฐานใน Java และ C # เหมือนกัน ไม่มีอะไรในสเปคที่พูดเช่นนั้น

1

สถานะข้อกำหนดภาษา Java (Java SE 7) :

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

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