การเอาชนะ Object.finalize () ไม่ดีจริงเหรอ?


34

ข้อโต้แย้งหลักสองข้อต่อการยกเลิกObject.finalize()คือ:

  1. คุณไม่ต้องตัดสินใจเมื่อมันถูกเรียก

  2. มันอาจจะไม่ถูกเรียกเลย

หากฉันเข้าใจสิ่งนี้อย่างถูกต้องฉันไม่คิดว่าสิ่งเหล่านี้เป็นเหตุผลที่ดีพอที่จะเกลียดObject.finalize()มาก

  1. มันขึ้นอยู่กับการนำ VM ไปใช้และ GC เพื่อพิจารณาว่าเวลาที่เหมาะสมในการจัดสรรคืนวัตถุนั้นไม่ใช่นักพัฒนา เหตุใดจึงสำคัญที่ต้องตัดสินใจเมื่อObject.finalize()ถูกเรียก

  2. ตามปกติและแก้ไขให้ถูกต้องหากฉันผิดเวลาที่Object.finalize()จะไม่ถูกเรียกก็คือเมื่อแอปพลิเคชันถูกยกเลิกก่อนที่ GC จะมีโอกาสเรียกใช้ อย่างไรก็ตามวัตถุได้ถูกจัดสรรคืนเมื่อกระบวนการของแอปพลิเคชันสิ้นสุดลง จึงObject.finalize()ไม่ได้รับการเรียกเนื่องจากไม่จำเป็นต้องโทร ทำไมนักพัฒนาถึงสนใจ?

ทุกครั้งที่ฉันใช้วัตถุที่ฉันต้องปิดเอง (เช่นตัวจัดการไฟล์และการเชื่อมต่อ) ฉันจะผิดหวังมาก ฉันต้องตรวจสอบอย่างต่อเนื่องว่าวัตถุมีการนำไปใช้close()หรือไม่และฉันแน่ใจว่าฉันไม่ได้รับสายบางครั้งในอดีต ทำไมมันไม่ได้เป็นเพียงที่เรียบง่ายและปลอดภัยในการเพียงแค่ปล่อยให้มันไป VM และ GC ในการกำจัดของวัตถุเหล่านี้โดยการวางclose()การดำเนินงานในObject.finalize()?


1
หมายเหตุ: เช่นเดียวกับ API จำนวนมากจากยุค Java 1.0 ความหมายของเธรดfinalize()นั้นค่อนข้างสับสน หากคุณเคยใช้มันต้องแน่ใจว่ามันปลอดภัยสำหรับเธรดที่เกี่ยวข้องกับวิธีการอื่น ๆ ทั้งหมดในวัตถุเดียวกัน
billc.cn

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

1
+1 กับคำถามนี้ คำตอบส่วนใหญ่ด้านล่างระบุว่าทรัพยากรเช่นตัวอธิบายไฟล์มี จำกัด และควรรวบรวมด้วยตนเอง สิ่งเดียวกันคือ / เป็นจริงเกี่ยวกับหน่วยความจำดังนั้นถ้าเรายอมรับการหน่วงเวลาบางอย่างในการรวบรวมหน่วยความจำทำไมไม่ยอมรับมันสำหรับตัวอธิบายไฟล์และ / หรือทรัพยากรอื่น ๆ
mbonnin

การพูดถึงย่อหน้าสุดท้ายของคุณคุณสามารถปล่อยให้ Java จัดการสิ่งที่ปิดเช่นตัวจัดการไฟล์และการเชื่อมต่อโดยใช้ความพยายามเพียงเล็กน้อยในส่วนของคุณ ใช้บล็อก try-with-resources - มันพูดถึงอีกสองสามครั้งแล้วในคำตอบและความคิดเห็น แต่ฉันคิดว่ามันคุ้มค่าที่นี่ บทช่วยสอนของ Oracle สำหรับสิ่งนี้สามารถดูได้ที่docs.oracle.com/javase/tutorial/essential/exceptions/ …
Jeutnarg

คำตอบ:


45

จากประสบการณ์ของฉันมีเหตุผลเพียงหนึ่งเดียวเท่านั้นที่เอาชนะObject.finalize()ได้ แต่เป็นเหตุผลที่ดีมาก :

ไปยังสถานที่รหัสข้อผิดพลาดในการเข้าสู่ที่แจ้งให้คุณทราบหากคุณลืมที่จะเรียกfinalize()close()

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

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

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

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

@Override
protected void finalize() throws Throwable
{
    if( Global.DEBUG && !closed )
    {
        Log.Error( "FORGOT TO CLOSE THIS!" );
    }
    //super.finalize(); see alip's comment on why this should not be invoked.
}

ความคิดที่อยู่เบื้องหลังนี้คือGlobal.DEBUGเป็นstatic finalตัวแปรที่มีค่าเป็นที่รู้จักกันในเวลารวบรวมดังนั้นถ้ามันเป็นfalseแล้วคอมไพเลอร์จะไม่ปล่อยรหัสใด ๆ เลยสำหรับทั้งifคำสั่งซึ่งจะทำให้เรื่องนี้เป็นที่น่ารำคาญ (ว่าง) finalizer ซึ่งในทางกลับกัน หมายความว่าชั้นเรียนของคุณจะได้รับการปฏิบัติเสมือนว่าไม่มีผู้เข้ารอบสุดท้าย (ใน C # สามารถทำได้ด้วย#if DEBUGบล็อกที่ดีแต่สิ่งที่เราทำได้คือ java ซึ่งเราจ่ายความเรียบง่ายที่ชัดเจนในโค้ดพร้อมค่าใช้จ่ายเพิ่มเติมในสมอง)

เพิ่มเติมเกี่ยวกับการกำจัดข้อบังคับที่มีการอภิปรายเพิ่มเติมเกี่ยวกับการกำจัดทรัพยากรใน dot Net ที่นี่: michael.gr: การกำจัดข้อบังคับและสิ่งที่น่ารังเกียจ "Dispos-dising"


2
@MikeNakis อย่าลืม closeable ถูกกำหนดให้เป็นทำอะไรถ้าเรียกว่าเป็นครั้งที่สอง: docs.oracle.com/javase/7/docs/api/java/io/Closeable.html ฉันยอมรับว่าบางครั้งฉันได้บันทึกคำเตือนเมื่อชั้นเรียนถูกปิดสองครั้ง แต่ในทางเทคนิคแล้วคุณไม่ควรทำเช่นนั้น แม้ว่าในทางเทคนิคแล้วการโทร. ปิด () บน Closable มากกว่าหนึ่งครั้งจะใช้ได้อย่างสมบูรณ์แบบ
Patrick M

1
@usr ทั้งหมดนี้ทำให้คุณเชื่อมั่นในการทดสอบของคุณหรือคุณไม่เชื่อใจการทดสอบของคุณ ถ้าคุณไม่ไว้ใจทดสอบของคุณนั่นเองไปข้างหน้าและประสบการสรุปค่าใช้จ่ายไปยัง close()เพียงในกรณีที่ ฉันเชื่อว่าถ้าการทดสอบของฉันไม่น่าเชื่อถือฉันก็ไม่ควรปล่อยระบบให้ใช้งานจริง
Mike Nakis

3
@ ไมค์สำหรับการif( Global.DEBUG && ...สร้างการทำงานดังนั้น JVM จะไม่สนใจfinalize()วิธีการที่ไม่สำคัญGlobal.DEBUGจะต้องตั้งค่าที่รวบรวมเวลา (ตรงข้ามกับการฉีดเป็นต้น) ดังนั้นสิ่งต่อไปนี้จะเป็นรหัสตาย การเรียกsuper.finalize()บล็อกนอกถ้าบล็อกนั้นเพียงพอสำหรับ JVM ที่จะปฏิบัติต่อมันในลักษณะที่ไม่สำคัญ (โดยไม่คำนึงถึงมูลค่าของGlobal.DEBUGอย่างน้อยใน HotSpot 1.8) แม้ว่าจะเป็นซุปเปอร์คลาส#finalize()ก็ตาม!
alip

1
@ ไมค์ฉันกลัวว่าเป็นกรณีนี้ ฉันทดสอบด้วย (การแก้ไขเล็กน้อยรุ่น) ของการทดสอบในบทความที่คุณเชื่อมโยงและเอาท์พุท GC verbose (พร้อมกับประสิทธิภาพที่น่าประหลาดใจ) ยืนยันว่าวัตถุจะถูกคัดลอกไปยังพื้นที่ผู้รอดชีวิต / เก่าและต้องการ GC เต็มจำนวนเพื่อรับ กำจัด.
alip

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

28

ทุกครั้งที่ฉันใช้วัตถุที่ฉันต้องปิดเอง (เช่นตัวจัดการไฟล์และการเชื่อมต่อ) ฉันจะผิดหวังมาก [ ... ] ทำไมมันไม่ได้เป็นเพียงที่เรียบง่ายและปลอดภัยในการเพียงแค่ปล่อยให้มันไป VM และ GC ในการกำจัดของวัตถุเหล่านี้โดยการวางclose()การดำเนินงานในObject.finalize()?

เนื่องจากตัวจัดการไฟล์ & การเชื่อมต่อ (นั่นคือfile descriptorsบนระบบ Linux & POSIX) เป็นทรัพยากรที่ค่อนข้างหายาก (คุณอาจถูก จำกัด ไว้ที่ 256 ของพวกเขาในบางระบบหรือ 16384 ที่อื่น ๆ ดูที่setrlimit (2) ) ไม่มีการรับประกันว่า GC จะถูกเรียกบ่อยครั้ง (หรือในเวลาที่เหมาะสม) เพื่อหลีกเลี่ยงการใช้ทรัพยากรที่ จำกัด เช่นนี้ และหาก GC ไม่ได้เรียกว่าเพียงพอ (หรือการสรุปไม่ทำงานในเวลาที่เหมาะสม) คุณจะถึงขีด จำกัด นั้น (อาจต่ำ)

การสรุปเป็นสิ่งที่ "ดีที่สุด" ใน JVM มันอาจจะไม่ถูกเรียกหรือถูกเรียกว่าค่อนข้างล่าช้า ... โดยเฉพาะถ้าคุณมี RAM จำนวนมากหรือหากโปรแกรมของคุณไม่จัดสรรวัตถุจำนวนมาก (หรือถ้าส่วนใหญ่ตายก่อนที่จะถูกส่งต่อไปยังที่เก่าพอสมควร การสร้างโดย generational GC ที่คัดลอก) GC อาจเรียกได้ว่าไม่ค่อยบ่อยนักและการสรุปอาจไม่ทำงานบ่อยครั้งนัก (หรืออาจไม่ได้ทำงานเลย)

ดังนั้นcloseไฟล์อธิบายอย่างชัดเจนถ้าเป็นไปได้ หากคุณกลัวที่จะรั่วไหลให้ใช้ขั้นตอนสุดท้ายเป็นมาตรการเพิ่มเติมไม่ใช่วิธีหลัก


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

2
ที่จริงแล้วขณะที่จำนวนของ filedescriptors ได้รับอนุญาตอาจจะต่ำที่ไม่จริงๆจุดเหนียวเพราะสามารถใช้เป็นสัญญาณสำหรับ GC อย่างน้อย สิ่งที่เป็นปัญหาจริงๆคือก) ไม่โปร่งใสกับ GC อย่างสิ้นเชิงว่าทรัพยากรที่ไม่มีการจัดการ GC ติดอยู่กับมันมากน้อยเพียงใดและข) ทรัพยากรเหล่านั้นจำนวนมากมีความเป็นเอกลักษณ์ดังนั้นผู้อื่นอาจถูกบล็อกหรือปฏิเสธ
Deduplicator

2
และหากคุณเปิดไฟล์ทิ้งไว้อาจเป็นการรบกวนผู้อื่นที่ใช้งานไฟล์ (โปรแกรมดู Windows 8 XPS ฉันกำลังมองคุณอยู่!)
Loren Pechtel

2
"หากคุณกลัวที่จะรั่วไหล [file descriptors] ให้ใช้การสรุปเป็นมาตรการเพิ่มเติมไม่ใช่ตัวหลัก" คำสั่งนี้ฟังดูแปลกสำหรับฉัน หากคุณออกแบบรหัสของคุณได้ดีคุณควรแนะนำรหัสที่ซ้ำซ้อนที่กระจายการล้างข้อมูลในหลาย ๆ ที่หรือไม่?
mucaho

2
@BasileStarynkevitch จุดของคุณคือว่าในโลกอุดมคติใช่ความซ้ำซ้อนไม่ดี แต่ในทางปฏิบัติที่คุณไม่สามารถมองเห็นทุกแง่มุมที่เกี่ยวข้องดีกว่าปลอดภัยกว่าขออภัย
mucaho

13

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

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

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


ถ้าฉันควรเขียนโค้ดที่ "จำเป็น" เท่านั้นฉันควรหลีกเลี่ยงการใส่สไตล์ทั้งหมดในแอปพลิเคชัน GUI ทั้งหมดของฉันหรือไม่ แน่นอนว่าไม่มีความจำเป็นเลย GUI จะทำงานได้ดีโดยไม่ต้องใส่สไตล์พวกเขาจะดูน่าสยดสยอง เกี่ยวกับ finalizer อาจจำเป็นต้องทำบางสิ่ง แต่ก็ยังโอเคที่จะใส่ลงใน finalizer เนื่องจาก finalizer จะถูกเรียกระหว่าง object GC ถ้าหากทั้งหมด หากคุณต้องการปิดทรัพยากรโดยเฉพาะอย่างยิ่งเมื่อพร้อมสำหรับการรวบรวมขยะแล้ว finalizer จะเหมาะสมที่สุด โปรแกรมจะยุติการปล่อยทรัพยากรของคุณหรือเรียกว่า finalizer
Kröw

ถ้าฉันมี RAM ที่มีน้ำหนักมากวัตถุที่ราคาแพงสร้างได้ใกล้และฉันตัดสินใจที่จะอ้างอิงมันอย่างอ่อน ๆ เพื่อที่จะสามารถเคลียร์ได้เมื่อไม่ต้องการฉันก็สามารถใช้finalize()เพื่อปิดมันได้ในกรณีที่วงจร gc ต้องการให้มันว่างเปล่า แกะ. มิฉะนั้นฉันจะเก็บวัตถุไว้ใน RAM แทนที่จะต้องสร้างใหม่และปิดมันทุกครั้งที่ฉันต้องใช้มัน แน่นอนว่าทรัพยากรที่เปิดขึ้นจะไม่ถูกปล่อยให้เป็นอิสระจนกว่าวัตถุนั้นจะถูก GCed ทุกครั้งที่อาจเป็นไปได้ แต่ฉันไม่จำเป็นต้องรับประกันว่าทรัพยากรของฉันจะได้รับการปลดปล่อยในเวลาใดเวลาหนึ่ง
Kröw

8

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

นอกจากนี้การใช้ลองกับทรัพยากรทำให้การปิดวัตถุ 'ปิดได้' เมื่อเสร็จสิ้นง่ายต่อการทำ หากคุณไม่คุ้นเคยกับโครงสร้างนี้ฉันขอแนะนำให้คุณตรวจสอบ: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


8

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

จากทฤษฎีและการปฏิบัติของ Java: การรวบรวมขยะและประสิทธิภาพ (Brian Goetz) Finalizers ไม่ใช่เพื่อนของคุณ :

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


จุดที่ดีเยี่ยม ดูคำตอบของฉันซึ่งให้วิธีการหลีกเลี่ยงค่าใช้จ่ายของfinalize()ประสิทธิภาพ
Mike Nakis

7

เหตุผลที่ฉันชอบที่สุด (อย่างน้อย) ในการหลีกเลี่ยงObject.finalizeไม่ใช่ว่าวัตถุจะได้รับการสรุปหลังจากที่คุณคาดหวัง แต่พวกเขาสามารถสรุปได้ก่อนที่คุณจะคาดหวัง ปัญหาไม่ใช่ว่าวัตถุที่ยังอยู่ในขอบเขตสามารถสรุปได้ก่อนที่จะออกจากขอบเขตหาก Java ตัดสินใจว่าไม่สามารถเข้าถึงได้อีกต่อไป

void test() {
   HasFinalize myObject = ...;
   OutputStream os = myObject.stream;

   // myObject is no-longer reachable at this point, 
   // even though it is in scope. But objects are finalized
   // based on reachability.
   // And since finalization is on another thread, it 
   // could happen before or in the middle of the write .. 
   // closing the stream and causing much fun.
   os.write("Hello World");
}

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


1
สิ่งนั้นคือHasFinalize.streamตัวของมันเองควรจะเป็นวัตถุสุดท้ายที่แยกได้ นั่นคือการสรุปของไม่ควรจบหรือพยายามที่จะทำความสะอาดHasFinalize streamหรือถ้ามันควรจะทำให้streamไม่สามารถเข้าถึงได้
acelent


4

ฉันต้องตรวจสอบอย่างต่อเนื่องว่าวัตถุมีการใช้งาน close () หรือไม่และฉันแน่ใจว่าฉันพลาดการรับสายไปสองสามครั้งในอดีต

ในคราสฉันได้รับการแจ้งเตือนทุกครั้งที่ผมลืมสิ่งที่ใกล้ที่ดำเนิน/Closeable AutoCloseableฉันไม่แน่ใจว่าเป็นสิ่งที่ Eclipse หรือเป็นส่วนหนึ่งของคอมไพเลอร์อย่างเป็นทางการ แต่คุณอาจใช้เครื่องมือวิเคราะห์แบบคงที่ที่คล้ายกันเพื่อช่วยคุณ ตัวอย่างเช่น FindBugs อาจช่วยให้คุณตรวจสอบว่าคุณลืมปิดทรัพยากรหรือไม่


1
AutoCloseableกล่าวถึงความคิดที่ดี ทำให้การจัดการทรัพยากรทำได้ง่ายขึ้นด้วยการลองกับทรัพยากร สิ่งนี้ทำให้ข้อโต้แย้งหลายข้อในคำถามเป็นโมฆะ

2

สำหรับคำถามแรกของคุณ:

มันขึ้นอยู่กับการนำ VM ไปใช้และ GC เพื่อพิจารณาว่าเวลาที่เหมาะสมในการจัดสรรคืนวัตถุนั้นไม่ใช่นักพัฒนา เหตุใดจึงสำคัญที่ต้องตัดสินใจเมื่อObject.finalize()ถูกเรียก

ดี JVM จะกำหนดเมื่อมันเป็นจุดที่ดีในการเรียกคืนหน่วยความจำที่ได้รับการจัดสรรสำหรับวัตถุ นี้ไม่จำเป็นต้องเวลาที่ทรัพยากรทำความสะอาด, คุณต้องการที่จะดำเนินการในfinalize(), ควรจะเกิดขึ้น นี่คือตัวอย่างในคำถาม“ finalize () ที่เรียกใช้บนวัตถุที่เข้าถึงได้อย่างมากใน Java 8”ใน SO มีclose()วิธีการที่ถูกเรียกโดยfinalize()วิธีการในขณะที่ความพยายามที่จะอ่านจากกระแสโดยวัตถุเดียวกันยังคงค้างอยู่ ดังนั้นนอกเหนือจากความเป็นไปได้ที่รู้จักกันดีที่finalize()ได้รับการเรียกวิธีไปสายมีความเป็นไปได้ที่มันถูกเรียกเร็วเกินไป

หลักฐานของคำถามที่สองของคุณ:

ตามปกติและแก้ไขให้ถูกต้องหากฉันผิดเวลาที่Object.finalize()จะไม่ถูกเรียกก็คือเมื่อแอปพลิเคชันถูกยกเลิกก่อนที่ GC จะมีโอกาสเรียกใช้

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

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

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

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

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