Dua argumen utama yang menentang penggantian Object.finalize()
adalah:
Anda tidak bisa memutuskan kapan itu dipanggil.
Mungkin tidak dipanggil sama sekali.
Jika saya memahami ini dengan benar, saya tidak berpikir itu adalah alasan yang cukup baik untuk membenci Object.finalize()
begitu banyak.
Terserah implementasi VM dan GC untuk menentukan kapan waktu yang tepat untuk mendelokasi objek, bukan pengembang. Mengapa penting untuk memutuskan kapan
Object.finalize()
dipanggil?Biasanya, dan koreksi saya jika saya salah, satu-satunya waktu
Object.finalize()
tidak akan dipanggil adalah ketika aplikasi dihentikan sebelum GC mendapat kesempatan untuk berjalan. Namun, objek itu tetap tidak dapat dialokasikan ketika proses aplikasi dihentikan dengannya. JadiObject.finalize()
tidak dipanggil karena tidak perlu dipanggil. Mengapa pengembang peduli?
Setiap kali saya menggunakan objek yang harus saya tutup secara manual (seperti menangani file dan koneksi), saya menjadi sangat frustrasi. Saya harus terus-menerus memeriksa apakah suatu objek memiliki implementasi close()
, dan saya yakin saya telah melewatkan beberapa panggilan ke sana di beberapa titik di masa lalu. Mengapa tidak lebih sederhana dan aman untuk menyerahkannya ke VM dan GC untuk membuang objek-objek ini dengan memasukkan close()
implementasinya Object.finalize()
?
sumber
finalize()
agak kacau. Jika Anda pernah mengimplementasikannya, pastikan itu aman untuk semua metode lain pada objek yang sama.Jawaban:
Dalam pengalaman saya, ada satu dan hanya satu alasan untuk mengganti
Object.finalize()
, tetapi itu adalah alasan yang sangat bagus :Analisis statis hanya dapat menangkap kelalaian dalam skenario penggunaan sepele, dan peringatan kompiler yang disebutkan dalam jawaban lain memiliki pandangan sederhana tentang hal-hal yang sebenarnya harus Anda nonaktifkan untuk menyelesaikan segala sesuatu yang non-sepele. (Saya memiliki lebih banyak peringatan yang diaktifkan daripada programmer lain yang saya kenal atau pernah dengar, tapi saya tidak mengaktifkan peringatan bodoh.)
Finalisasi tampaknya menjadi mekanisme yang baik untuk memastikan bahwa sumber daya tidak rusak, tetapi kebanyakan orang melihatnya dengan cara yang benar-benar salah: mereka menganggapnya sebagai mekanisme cadangan alternatif, perlindungan "kesempatan kedua" yang secara otomatis akan menyelamatkan hari dengan membuang sumber daya yang mereka lupa. Ini salah besar . Hanya ada satu cara untuk melakukan sesuatu: Anda selalu menutup semuanya, atau finalisasi selalu menutup semuanya. Tetapi karena finalisasi tidak dapat diandalkan, finalisasi tidak bisa seperti itu.
Jadi, ada skema ini yang saya sebut Pembuangan Wajib , dan menetapkan bahwa programmer bertanggung jawab untuk selalu secara eksplisit menutup segala sesuatu yang mengimplementasikan
Closeable
atauAutoCloseable
. (Pernyataan coba-dengan-sumber daya masih dianggap sebagai penutupan eksplisit.) Tentu saja, programmer mungkin lupa, jadi di situlah finalisasi berperan, tetapi bukan sebagai peri ajaib yang secara ajaib akan membuat segalanya benar pada akhirnya: Jika finalisasi menemukan yangclose()
tidak dipanggil, itu tidakmencoba untuk memohonnya, justru karena akan ada (dengan kepastian matematika) ada gerombolan programmer n00b yang akan bergantung padanya untuk melakukan pekerjaan yang mereka terlalu malas atau terlalu linglung untuk melakukannya. Jadi, dengan pembuangan wajib, ketika finalisasi menemukan yangclose()
tidak diminta, itu mencatat pesan kesalahan merah terang, memberitahu programmer dengan huruf besar semua modal untuk memperbaiki dirinya, barang-barangnya.Sebagai manfaat tambahan, rumor mengatakan bahwa "JVM akan mengabaikan metode finalisasi () sepele (misalnya yang hanya kembali tanpa melakukan apa pun, seperti yang didefinisikan dalam kelas Object)", sehingga dengan pembuangan wajib Anda dapat menghindari semua finalisasi overhead di seluruh sistem Anda ( lihat jawaban alip untuk informasi tentang seberapa buruk overhead ini) dengan mengkode
finalize()
metode Anda seperti ini:Gagasan di balik ini adalah bahwa
Global.DEBUG
adalahstatic final
variabel yang nilainya diketahui pada waktu kompilasi, jadi jika itufalse
maka kompiler tidak akan memancarkan kode sama sekali untuk seluruhif
pernyataan, yang akan membuat ini finalizer sepele (kosong), yang pada gilirannya berarti bahwa kelas Anda akan diperlakukan seolah-olah tidak memiliki finalizer. (Dalam C # ini akan dilakukan dengan#if DEBUG
blok yang bagus , tapi apa yang bisa kita lakukan, ini java, di mana kita membayar kesederhanaan yang jelas dalam kode dengan overhead tambahan di otak.)Lebih lanjut tentang Pembuangan Wajib, dengan diskusi tambahan tentang membuang sumber daya di dot Net, di sini: michael.gr: Pembuangan wajib vs kekejian "Buang-buang"
sumber
close()
, untuk berjaga-jaga. Saya percaya bahwa jika pengujian saya tidak dapat dipercaya, maka saya lebih baik tidak merilis sistem untuk produksi.if( Global.DEBUG && ...
konstruk berfungsi sehingga JVM akan mengabaikanfinalize()
metode ini sebagai sepele,Global.DEBUG
harus ditetapkan pada waktu kompilasi (sebagai lawan disuntikkan dll) sehingga yang berikut ini akan menjadi kode mati. Panggilan kesuper.finalize()
luar blok if juga cukup bagi JVM untuk memperlakukannya sebagai non-sepele (berapapun nilainyaGlobal.DEBUG
, setidaknya pada HotSpot 1.8) bahkan jika kelas supernya#finalize()
juga sepele!java.io
. Jika pengaman benang semacam itu tidak ada dalam daftar keinginan, itu menambah overhead yang disebabkan olehfinalize()
...Karena penanganan file & koneksi (yaitu, deskriptor file pada sistem Linux & POSIX) adalah sumber daya yang langka (Anda mungkin terbatas pada 256 di antaranya pada beberapa sistem, atau 16.384 pada beberapa sistem lain, lihat setrlimit (2) ). Tidak ada jaminan bahwa GC akan dipanggil cukup sering (atau pada waktu yang tepat) untuk menghindari melelahkan sumber daya yang terbatas. Dan jika GC tidak disebut cukup (atau finalisasi tidak berjalan pada waktu yang tepat) Anda akan mencapai batas (mungkin rendah) itu.
Finalisasi adalah hal "upaya terbaik" di JVM. Itu mungkin tidak dipanggil, atau disebut sangat terlambat ... Secara khusus, jika Anda memiliki banyak RAM atau jika program Anda tidak mengalokasikan banyak objek (atau jika sebagian besar dari mereka mati sebelum diteruskan ke beberapa yang cukup tua) generasi dengan menyalin generational GC), GC bisa disebut sangat jarang, dan finalisasi mungkin tidak berjalan sangat sering (atau bahkan mungkin tidak berjalan sama sekali).
Jadi
close
deskriptor file secara eksplisit, jika mungkin. Jika Anda takut bocor, gunakan finalisasi sebagai langkah ekstra, bukan yang utama.sumber
Lihatlah masalah ini dengan cara ini: Anda hanya boleh menulis kode yang (a) benar (jika tidak, program Anda salah) dan (b) perlu (jika kode Anda terlalu besar, yang berarti lebih banyak RAM diperlukan, lebih banyak siklus dihabiskan untuk hal-hal yang tidak berguna, lebih banyak upaya untuk memahaminya, lebih banyak waktu dihabiskan untuk mempertahankannya dll.)
Sekarang pertimbangkan apa yang ingin Anda lakukan dalam finalizer. Entah itu perlu. Dalam hal ini Anda tidak dapat memasukkannya ke finalizer, karena Anda tidak tahu apakah itu akan dipanggil. Itu tidak cukup baik. Atau itu tidak perlu - maka Anda tidak harus menulis itu di tempat pertama! Either way, memasukkannya ke finalizer adalah pilihan yang salah.
(Perhatikan bahwa contoh Anda nama, seperti menutup file stream, lihat seolah-olah mereka tidak benar-benar diperlukan, tetapi mereka. Hanya saja tidak sampai Anda menekan batas untuk menangani file yang terbuka pada sistem Anda, Anda akan melihat bahwa Anda kode tidak benar. Tapi batas itu adalah fitur dari sistem operasi dan karena itu lebih tak terduga dari kebijakan JVM untuk finalizers, sehingga benar-benar, benar-benar adalah penting bahwa Anda tidak membuang menangani file.)
sumber
finalize()
untuk menutupnya jika siklus gc benar-benar perlu membebaskan RAM Kalau tidak, saya akan menyimpan objek dalam RAM daripada harus membuat ulang dan menutupnya setiap kali saya perlu menggunakannya. Tentu, sumber daya yang dibuka tidak akan dibebaskan sampai objek GCed, kapan pun itu, tetapi saya mungkin tidak perlu menjamin bahwa sumber daya saya akan dibebaskan pada waktu tertentu.Salah satu alasan terbesar untuk tidak bergantung pada finalizer adalah bahwa sebagian besar sumber daya yang mungkin tergoda untuk dibersihkan oleh finalizer sangat terbatas. Pengumpul sampah hanya berjalan sesering mungkin, karena melintasi referensi untuk menentukan apakah sesuatu dapat dikeluarkan mahal atau tidak. Ini berarti bahwa itu bisa 'sementara' sebelum benda Anda benar-benar dihancurkan. Jika Anda memiliki banyak objek yang membuka koneksi database berumur pendek, misalnya, membiarkan finalizer untuk membersihkan koneksi ini dapat menghabiskan kumpulan koneksi Anda sementara menunggu pengumpul sampah untuk akhirnya menjalankan dan membebaskan koneksi yang sudah selesai. Kemudian, karena menunggu, Anda berakhir dengan tumpukan besar permintaan yang antri, yang dengan cepat menghabiskan kumpulan koneksi lagi. Saya t'
Selain itu, penggunaan coba-dengan-sumber daya membuat menutup objek 'tertutup' pada penyelesaian mudah dilakukan. Jika Anda tidak terbiasa dengan konstruk ini, saya sarankan Anda memeriksanya: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
sumber
Selain mengapa menyerahkannya kepada finalizer untuk melepaskan sumber daya umumnya merupakan ide yang buruk, objek yang dapat diselesaikan dilengkapi dengan overhead kinerja.
Dari teori dan praktik Jawa: Pengumpulan dan kinerja sampah (Brian Goetz), Penyelesai bukan teman Anda :
sumber
finalize()
.Alasan favorit saya (paling tidak) untuk menghindarinya
Object.finalize
adalah bukan karena objek mungkin selesai setelah Anda harapkan, tetapi mereka bisa diselesaikan sebelum Anda mungkin mengharapkannya. Masalahnya bukan bahwa objek yang masih dalam cakupan dapat diselesaikan sebelum lingkup keluar jika Java memutuskan itu tidak lagi dapat dijangkau.Lihat pertanyaan ini untuk lebih jelasnya. Yang lebih menyenangkan adalah bahwa keputusan ini mungkin hanya dibuat setelah optimasi hot-spot dimulai, membuat ini menyakitkan untuk di-debug.
sumber
HasFinalize.stream
itu sendiri harus menjadi objek yang dapat diselesaikan secara terpisah. Artinya, finalisasiHasFinalize
seharusnya tidak menyelesaikan atau mencoba membersihkanstream
. Atau jika harus, maka itu harus membuatstream
tidak dapat diakses.Di Eclipse, saya mendapat peringatan setiap kali saya lupa untuk menutup sesuatu yang mengimplementasikan
Closeable
/AutoCloseable
. Saya tidak yakin apakah itu suatu hal Eclipse atau apakah itu bagian dari kompiler resmi, tetapi Anda mungkin melihat ke dalam menggunakan alat analisis statis serupa untuk membantu Anda di sana. FindBugs, misalnya, mungkin dapat membantu Anda memeriksa apakah Anda lupa menutup sumber daya.sumber
AutoCloseable
. Itu membuat otak-mati-mudah untuk mengelola sumber daya dengan coba-dengan-sumber daya. Ini membatalkan beberapa argumen dalam pertanyaan.Untuk pertanyaan pertama Anda:
Nah, JVM akan menentukan, kapan saat yang tepat untuk merebut kembali penyimpanan yang telah dialokasikan untuk suatu objek. Ini belum tentu waktu ketika sumber daya dibersihkan, Anda ingin melakukan
finalize()
, harus terjadi. Ini diilustrasikan dalam pertanyaan "finalisasi () dipanggil pada objek yang sangat dapat dijangkau di Java 8" pada SO. Di sana, suatuclose()
metode telah dipanggil oleh suatufinalize()
metode, sementara upaya untuk membaca dari aliran oleh objek yang sama masih tertunda. Jadi selain kemungkinan terkenal yangfinalize()
dipanggil terlambat, ada kemungkinan dipanggil terlalu dini.Premis dari pertanyaan kedua Anda:
itu salah. Tidak ada persyaratan untuk JVM untuk mendukung finalisasi sama sekali. Yah, itu tidak sepenuhnya salah karena Anda masih bisa menafsirkannya sebagai "aplikasi dihentikan sebelum finalisasi berlangsung", dengan asumsi bahwa aplikasi Anda akan pernah berakhir.
Tetapi perhatikan perbedaan kecil antara "GC" dari pernyataan asli Anda dan istilah "finalisasi". Pengumpulan sampah berbeda dengan finalisasi. Setelah manajemen memori mendeteksi bahwa suatu objek tidak dapat dijangkau, ia dapat dengan mudah mendapatkan kembali ruangnya, jika salah satunya, ia tidak memiliki
finalize()
metode khusus atau finalisasi tidak didukung, atau mungkin membuat objek untuk difinalisasi selesai. Dengan demikian, penyelesaian siklus pengumpulan sampah tidak menyiratkan bahwa penyelesai dieksekusi. Itu mungkin terjadi di lain waktu, ketika antrian diproses, atau tidak pernah sama sekali.Poin ini juga menjadi alasan mengapa bahkan pada JVM dengan dukungan finalisasi, mengandalkannya untuk pembersihan sumber daya adalah berbahaya. Pengumpulan sampah adalah bagian dari manajemen memori dan karenanya dipicu oleh kebutuhan memori. Mungkin saja pengumpulan sampah tidak pernah berjalan karena ada cukup memori selama runtime keseluruhan (yah, itu masih cocok dengan "aplikasi dihentikan sebelum GC mendapat kesempatan untuk menjalankan" deskripsi, entah bagaimana). Ada kemungkinan juga bahwa GC berjalan tetapi setelah itu, ada cukup banyak memori yang direklamasi, sehingga antrian finalizer tidak diproses.
Dengan kata lain, sumber daya asli yang dikelola dengan cara ini masih merupakan hal asing bagi manajemen memori. Meskipun dijamin bahwa
OutOfMemoryError
hanya dibuang setelah upaya yang cukup untuk mengosongkan memori, itu tidak berlaku untuk sumber daya asli dan finalisasi. Ada kemungkinan bahwa membuka file gagal karena sumber daya tidak mencukupi sementara antrian finalisasi penuh dengan objek yang dapat membebaskan sumber daya ini, jika pemodal pernah berlari ...sumber