Hari ini kolega saya dan saya berdiskusi tentang penggunaan file final
kata kunci di Java untuk meningkatkan pengumpulan sampah.
Misalnya, jika Anda menulis metode seperti:
public Double doCalc(final Double value)
{
final Double maxWeight = 1000.0;
final Double totalWeight = maxWeight * value;
return totalWeight;
}
Mendeklarasikan variabel dalam metode final
akan membantu pengumpulan sampah untuk membersihkan memori dari variabel yang tidak digunakan dalam metode setelah metode keluar.
Apakah ini benar?
java
garbage-collection
final
Goran Martinic
sumber
sumber
Jawaban:
Berikut adalah contoh yang sedikit berbeda, satu dengan bidang jenis referensi akhir daripada variabel lokal jenis nilai akhir:
public class MyClass { public final MyOtherObject obj; }
Setiap kali Anda membuat instance MyClass, Anda akan membuat referensi keluar ke instance MyOtherObject, dan GC harus mengikuti tautan tersebut untuk mencari objek langsung.
JVM menggunakan algoritme GC mark-sweep, yang harus memeriksa semua referensi langsung di lokasi "akar" GC (seperti semua objek dalam tumpukan panggilan saat ini). Setiap benda hidup "ditandai" sebagai hidup, dan setiap benda yang dirujuk oleh benda hidup juga ditandai sebagai hidup.
Setelah menyelesaikan fase mark, GC menyapu heap, membebaskan memori untuk semua objek yang tidak ditandai (dan memadatkan memori untuk objek aktif yang tersisa).
Selain itu, penting untuk mengetahui bahwa memori heap Java dipartisi menjadi "generasi muda" dan "generasi tua". Semua objek pada awalnya dialokasikan untuk generasi muda (kadang-kadang disebut sebagai "kamar bayi"). Karena sebagian besar objek berumur pendek, GC lebih agresif dalam membebaskan sampah terbaru dari generasi muda. Jika sebuah objek bertahan dalam siklus pengumpulan generasi muda, ia akan dipindahkan ke generasi lama (terkadang disebut sebagai "generasi bertenor"), yang lebih jarang diproses.
Jadi, di luar kepala saya, saya akan mengatakan "tidak, modifer 'terakhir' tidak membantu GC mengurangi beban kerjanya".
Menurut pendapat saya, strategi terbaik untuk mengoptimalkan manajemen memori Anda di Java adalah dengan menghilangkan referensi palsu secepat mungkin. Anda dapat melakukannya dengan menetapkan "null" ke referensi objek segera setelah Anda selesai menggunakannya.
Atau, lebih baik lagi, minimalkan ukuran setiap cakupan deklarasi. Misalnya, jika Anda mendeklarasikan sebuah objek di awal metode 1000-baris, dan jika objek tetap hidup hingga penutupan cakupan metode itu (tanda kurung kurawal penutup terakhir), objek mungkin tetap hidup lebih lama dari yang sebenarnya. perlu.
Jika Anda menggunakan metode kecil, dengan hanya selusin baris kode, maka objek yang dideklarasikan di dalam metode itu akan keluar dari cakupan lebih cepat, dan GC akan dapat melakukan sebagian besar pekerjaannya dalam lingkungan yang jauh lebih efisien. generasi muda. Anda tidak ingin objek dipindahkan ke generasi yang lebih tua kecuali benar-benar diperlukan.
sumber
Mendeklarasikan variabel lokal
final
tidak akan memengaruhi pengumpulan sampah, itu hanya berarti Anda tidak dapat mengubah variabel. Contoh Anda di atas tidak boleh dikompilasi saat Anda memodifikasi variabeltotalWeight
yang telah ditandaifinal
. Di sisi lain, mendeklarasikan primitif (double
bukanDouble
)final
akan memungkinkan variabel tersebut dimasukkan ke dalam kode panggilan, sehingga dapat menyebabkan peningkatan memori dan kinerja. Ini digunakan ketika Anda memiliki nomorpublic static final Strings
di kelas.Secara umum, compiler dan runtime akan mengoptimalkannya jika memungkinkan. Cara terbaik adalah menulis kode dengan tepat dan tidak mencoba terlalu rumit. Gunakan
final
jika Anda tidak ingin variabel diubah. Asumsikan bahwa setiap pengoptimalan mudah akan dilakukan oleh kompilator, dan jika Anda khawatir tentang kinerja atau penggunaan memori, gunakan profiler untuk menentukan masalah sebenarnya.sumber
Tidak, itu sama sekali tidak benar.
Ingat itu
final
tidak berarti konstan, itu hanya berarti Anda tidak dapat mengubah referensi.final MyObject o = new MyObject(); o.setValue("foo"); // Works just fine o = new MyObject(); // Doesn't work.
Mungkin ada beberapa pengoptimalan kecil berdasarkan pengetahuan bahwa JVM tidak perlu mengubah referensi (seperti tidak memiliki pemeriksaan untuk melihat apakah telah berubah) tetapi akan sangat kecil sehingga tidak perlu khawatir.
Final
harus dianggap sebagai meta-data yang berguna bagi pengembang dan bukan sebagai pengoptimalan kompiler.sumber
Beberapa hal yang perlu diperhatikan:
Menghapus referensi tidak akan membantu GC. Jika ya, itu akan menunjukkan bahwa variabel Anda memiliki cakupan yang berlebihan. Satu pengecualian adalah kasus nepotisme objek.
Belum ada alokasi on-stack di Java.
Mendeklarasikan variabel final berarti Anda tidak dapat (dalam kondisi normal) memberikan nilai baru ke variabel tersebut. Karena final tidak mengatakan apa-apa tentang ruang lingkup, itu tidak mengatakan apa pun tentang efeknya pada GC.
sumber
Yah, saya tidak tahu tentang penggunaan pengubah "final" dalam kasus ini, atau pengaruhnya pada GC.
Tetapi saya dapat memberi tahu Anda ini: penggunaan nilai Kotak daripada primitif (misalnya, Double, bukan double) akan mengalokasikan objek tersebut di heap daripada tumpukan, dan akan menghasilkan sampah yang tidak perlu yang harus dibersihkan oleh GC.
Saya hanya menggunakan primitif dalam kotak ketika diperlukan oleh API yang ada, atau ketika saya membutuhkan primatif yang dapat dibatalkan.
sumber
Variabel akhir tidak dapat diubah setelah tugas awal (diberlakukan oleh kompilator).
Ini tidak mengubah perilaku pengumpulan sampah seperti itu. Satu-satunya masalah adalah bahwa variabel-variabel ini tidak dapat dinihilkan ketika tidak digunakan lagi (yang dapat membantu pengumpulan sampah dalam situasi memori yang ketat).
Anda harus tahu bahwa final memungkinkan compiler membuat asumsi tentang apa yang harus dioptimalkan. Kode sebaris dan tidak termasuk kode yang diketahui tidak dapat dijangkau.
final boolean debug = false; ...... if (debug) { System.out.println("DEBUG INFO!"); }
Println tidak akan dimasukkan dalam kode byte.
sumber
Ada kasus sudut yang tidak begitu terkenal dengan pengumpul sampah generasi. (Untuk penjelasan singkat baca jawaban benjismith untuk wawasan yang lebih dalam, baca artikel di bagian akhir).
Gagasan dalam GC generasi adalah bahwa sebagian besar waktu hanya perlu mempertimbangkan generasi muda. Lokasi root dipindai untuk referensi, dan kemudian objek generasi muda dipindai. Selama penyapuan yang lebih sering ini, tidak ada objek di generasi lama yang diperiksa.
Sekarang, masalahnya berasal dari fakta bahwa suatu objek tidak diperbolehkan memiliki referensi ke objek yang lebih muda. Ketika objek berumur panjang (generasi lama) mendapat referensi ke objek baru, referensi itu harus secara eksplisit dilacak oleh pengumpul sampah (lihat artikel dari IBM di hotspot JVM collector ), yang sebenarnya memengaruhi kinerja GC.
Alasan mengapa objek lama tidak bisa merujuk ke yang lebih muda adalah karena, karena objek lama tidak diperiksa dalam koleksi kecil, jika satu-satunya referensi ke objek disimpan di objek lama, itu tidak akan ditandai, dan akan salah. dialokasikan selama tahap penyisiran.
Tentu saja, seperti yang ditunjukkan oleh banyak orang, kata kunci terakhir tidak secara nyata memengaruhi pengumpul sampah, tetapi menjamin bahwa referensi tidak akan pernah diubah menjadi objek yang lebih muda jika objek ini bertahan dari koleksi kecil dan membuatnya ke heap yang lebih lama.
Artikel:
IBM pada pengumpulan sampah: sejarah , JVM hotspot dan kinerja . Ini mungkin tidak lagi sepenuhnya valid, karena sudah ada sejak 2003/04, tetapi memberikan beberapa wawasan yang mudah dibaca tentang GC.
Sun on Tuning pengumpulan sampah
sumber
GC bertindak atas ref yang tidak dapat dijangkau. Ini tidak ada hubungannya dengan "final", yang hanya merupakan penegasan tugas satu kali. Apakah mungkin bahwa beberapa VM GC dapat menggunakan "final"? Saya tidak mengerti bagaimana atau mengapa.
sumber
final
pada variabel dan parameter lokal tidak membuat perbedaan pada file kelas yang dihasilkan, sehingga tidak dapat mempengaruhi kinerja runtime. Jika sebuah kelas tidak memiliki subclass, HotSpot memperlakukan kelas itu seolah-olah itu sudah final (ia dapat membatalkannya nanti jika kelas yang melanggar asumsi itu dimuat). Saya percayafinal
pada metode hampir sama dengan kelas.final
pada bidang statis memungkinkan variabel diinterpretasikan sebagai "konstanta waktu kompilasi" dan pengoptimalan dilakukan oleh javac atas dasar itu.final
on field memungkinkan JVM memiliki beberapa kebebasan untuk mengabaikan hubungan yang terjadi-sebelum .sumber
Sepertinya ada banyak jawaban yang hanya merupakan dugaan yang mengembara. Sebenarnya, tidak ada pengubah akhir untuk variabel lokal di tingkat bytecode. Mesin virtual tidak akan pernah tahu bahwa variabel lokal Anda ditetapkan sebagai final atau tidak.
Jawaban atas pertanyaan Anda adalah tidak.
sumber
final int x; if (cond) x=1; else x=2;
). Oleh karena itu, compiler tanpa kata kunci terakhir dapat menjamin bahwa suatu variabel bersifat final atau tidak.Semua metode dan variabel dapat diganti secara default di subkelas. Jika kita ingin menyimpan subkelas dari menimpa anggota superclass, kita dapat mendeklarasikannya sebagai final menggunakan kata kunci final. Misalnya-
final int a=10;
final void display(){......}
Membuat metode akhir memastikan bahwa fungsionalitas yang ditentukan dalam superclass tidak akan pernah berubah. Demikian pula nilai variabel final tidak pernah bisa diubah. Variabel akhir berperilaku seperti variabel kelas.sumber
Sebenarnya tentang bidang contoh ,
final
mungkin sedikit meningkatkan kinerja jika GC tertentu ingin memanfaatkannya. KetikaGC
terjadi bersamaan (itu berarti aplikasi Anda masih berjalan, sementara GC sedang berlangsung ), lihat ini untuk penjelasan yang lebih luas , GC harus menerapkan hambatan tertentu saat menulis dan / atau membaca selesai. Tautan yang saya berikan cukup banyak menjelaskan hal itu, tetapi untuk membuatnya sangat pendek: ketika aGC
melakukan beberapa pekerjaan bersamaan, semua membaca dan menulis ke heap (sementara GC itu sedang berlangsung), "dicegat" dan diterapkan nanti; sehingga fase GC bersamaan dapat menyelesaikan pekerjaannya.Untuk
final
bidang Misalnya, karena mereka tidak dapat diubah (kecuali refleksi), hambatan ini dapat dihilangkan. Dan ini bukan hanya teori murni.Shenandoah GC
memiliki mereka dalam praktek (meskipun tidak lama ), dan Anda dapat melakukan, misalnya:Dan akan ada pengoptimalan dalam algoritma GC yang akan membuatnya sedikit lebih cepat. Ini karena tidak akan ada penghalang yang menghalangi
final
, karena tidak ada yang boleh memodifikasinya, selamanya. Bahkan tidak melalui refleksi atau JNI.sumber
Satu-satunya hal yang dapat saya pikirkan adalah bahwa kompilator dapat mengoptimalkan variabel akhir dan menyebariskannya sebagai konstanta ke dalam kode, sehingga Anda tidak akan mendapatkan alokasi memori.
sumber
Tentu saja, selama hidup objek lebih pendek yang menghasilkan manfaat besar dari manajemen memori, baru-baru ini kami memeriksa fungsionalitas ekspor yang memiliki variabel instance pada satu pengujian dan pengujian lain yang memiliki variabel lokal level metode. selama pengujian beban, JVM mengeluarkan outofmemoryerror pada pengujian pertama dan JVM dihentikan. Namun pada pengujian kedua, berhasil mendapatkan laporan karena manajemen memori yang lebih baik.
sumber
Satu-satunya saat saya lebih suka mendeklarasikan variabel lokal sebagai final adalah ketika:
Saya harus membuatnya final sehingga dapat dibagikan dengan beberapa kelas anonim (misalnya: membuat utas daemon dan membiarkannya mengakses beberapa nilai dari metode melampirkan)
Saya ingin membuatnya final (misalnya: beberapa nilai yang tidak boleh / tidak diganti karena kesalahan)
sumber