Di Jawa, segera setelah sebuah objek tidak lagi memiliki referensi, ia menjadi layak untuk dihapus, tetapi JVM memutuskan kapan objek tersebut benar-benar dihapus. Untuk menggunakan terminologi Objective-C, semua referensi Java secara inheren "kuat". Namun, dalam Objective-C, jika suatu objek tidak lagi memiliki referensi yang kuat, objek tersebut segera dihapus. Mengapa ini tidak terjadi di Jawa?
java
garbage-collection
moonman239
sumber
sumber
Jawaban:
Pertama-tama, Java memiliki referensi yang lemah dan kategori upaya terbaik lainnya yang disebut referensi lembut. Referensi lemah vs kuat adalah masalah yang sepenuhnya terpisah dari penghitungan referensi vs pengumpulan sampah.
Kedua, ada pola dalam penggunaan memori yang dapat membuat pengumpulan sampah lebih efisien dalam waktu dengan mengorbankan ruang. Misalnya, objek yang lebih baru jauh lebih mungkin dihapus daripada objek yang lebih lama. Jadi, jika Anda menunggu sedikit di antara sapuan, Anda dapat menghapus sebagian besar memori generasi baru, sambil memindahkan beberapa korban ke penyimpanan jangka panjang. Penyimpanan jangka panjang itu dapat dipindai lebih jarang. Penghapusan langsung melalui manajemen memori manual atau penghitungan referensi jauh lebih rentan terhadap fragmentasi.
Ini seperti perbedaan antara berbelanja bahan pangan satu kali per gaji, dan pergi setiap hari untuk mendapatkan makanan yang cukup untuk satu hari. Satu perjalanan besar Anda akan memakan waktu lebih lama daripada perjalanan kecil individual, tetapi secara keseluruhan Anda akhirnya menghemat waktu dan mungkin uang.
sumber
Karena mengetahui sesuatu dengan benar tidak lagi dirujuk tidak mudah. Bahkan tidak mendekati mudah.
Bagaimana jika Anda memiliki dua objek yang saling rujukan? Apakah mereka tinggal selamanya? Memperluas garis pemikiran untuk menyelesaikan struktur data yang sewenang-wenang, dan Anda akan segera melihat mengapa JVM atau pengumpul sampah lainnya dipaksa untuk menggunakan metode yang jauh lebih canggih untuk menentukan apa yang masih dibutuhkan dan apa yang bisa berjalan.
sumber
AFAIK, spesifikasi JVM (ditulis dalam bahasa Inggris) tidak menyebutkan kapan objek (atau nilai) yang tepat harus dihapus, dan membiarkannya untuk implementasi (demikian juga untuk R5RS ). Entah bagaimana itu membutuhkan atau menyarankan seorang pemulung, tetapi menyerahkan detailnya pada implementasinya. Dan juga untuk spesifikasi Java.
Ingat bahwa bahasa pemrograman adalah spesifikasi ( sintaks , semantik , dll ...), bukan implementasi perangkat lunak. Bahasa seperti Java (atau JVM) memiliki banyak implementasi. Spesifikasinya diterbitkan , dapat diunduh (sehingga Anda dapat mempelajarinya) dan ditulis dalam bahasa Inggris. §2.5.3 Tumpukan spesifikasi JVM menyebutkan seorang pemulung:
(Penekanan adalah milik saya; Finalisasi BTW disebutkan dalam §12.6 dari spesifikasi Java, dan model memori ada di §17.4 dari spesifikasi Java)
Jadi (di Jawa) Anda seharusnya tidak peduli ketika suatu objek dihapus , dan Anda bisa mengkodekan as-jika itu tidak terjadi (dengan alasan dalam abstraksi di mana Anda mengabaikannya). Tentu saja Anda perlu memperhatikan konsumsi memori dan set objek hidup, yang merupakan pertanyaan yang berbeda . Dalam beberapa kasus sederhana (pikirkan program "hello world") Anda dapat membuktikan - atau meyakinkan diri sendiri - bahwa memori yang dialokasikan agak kecil (misalnya kurang dari satu gigabyte), dan kemudian Anda tidak peduli sama sekali tentang penghapusan objek individual . Dalam lebih banyak kasus, Anda dapat meyakinkan diri Anda bahwa benda itu hidup(atau yang dapat dijangkau, yang merupakan superset - lebih mudah untuk alasan - hidup) tidak pernah melebihi batas yang wajar (dan kemudian Anda benar-benar mengandalkan GC, tetapi Anda tidak peduli bagaimana dan kapan pengumpulan sampah terjadi). Baca tentang kompleksitas ruang .
Saya kira pada beberapa implementasi JVM yang menjalankan program Java berumur pendek seperti hello world one, pengumpul sampah tidak dipicu sama sekali dan tidak ada penghapusan terjadi. AFAIU, perilaku seperti itu sesuai dengan banyak spesifikasi Java.
Sebagian besar implementasi JVM menggunakan teknik penyalinan generasional (setidaknya untuk sebagian besar objek Java, yang tidak menggunakan finalisasi atau referensi lemah ; dan finalisasi tidak dijamin akan terjadi dalam waktu singkat dan dapat ditunda, jadi ini hanya fitur yang bermanfaat bahwa kode Anda tidak boleh sangat bergantung pada) di mana gagasan menghapus objek individual tidak masuk akal (karena blok besar memori - yang berisi zona memori untuk banyak objek -, mungkin beberapa megabyte sekaligus, dapat dilepaskan sekaligus).
Jika spesifikasi JVM mengharuskan setiap objek untuk dihapus sesegera mungkin (atau hanya menempatkan lebih banyak kendala pada penghapusan objek), teknik GC generasi yang efisien akan dilarang, dan perancang Java dan JVM telah bijaksana dalam menghindarinya.
BTW, bisa jadi mungkin JVM naif yang tidak pernah menghapus objek dan tidak melepaskan memori mungkin sesuai dengan spesifikasi (surat, bukan semangat) dan tentu saja dapat menjalankan hal halo dunia dalam praktiknya (perhatikan bahwa sebagian besar program Java yang kecil dan berumur pendek mungkin tidak mengalokasikan lebih dari beberapa gigabytes memori). Tentu saja seperti JVM tidak layak disebut dan hanya hal mainan (seperti yang ini pelaksanaan
malloc
untuk C). Lihat Epsilon NoOp GC untuk informasi lebih lanjut. JVM kehidupan nyata adalah perangkat lunak yang sangat kompleks dan memadukan beberapa teknik pengumpulan sampah.Juga, Java tidak sama dengan JVM, dan Anda memiliki implementasi Java berjalan tanpa JVM (misalnya depan-of-waktu compiler Java, runtime Android ). Dalam beberapa kasus (sebagian besar yang akademis), Anda mungkin membayangkan (disebut teknik "pengumpulan sampah waktu") bahwa program Java tidak mengalokasikan atau menghapus pada saat runtime (misalnya karena kompilator pengoptimal telah cukup pintar untuk hanya menggunakan tumpukan panggilan dan variabel otomatis ).
Karena spesifikasi Java dan JVM tidak memerlukan itu.
Baca buku pegangan GC untuk informasi lebih lanjut (dan spesifikasi JVM ). Perhatikan bahwa menjadi hidup (atau berguna untuk perhitungan di masa depan) untuk suatu objek adalah properti seluruh program (non-modular).
Objective-C mendukung pendekatan penghitungan referensi untuk manajemen memori . Dan itu juga memiliki jebakan (misalnya programmer Objective-C harus peduli tentang referensi melingkar dengan menjelaskan referensi yang lemah, tetapi JVM menangani referensi melingkar dengan baik dalam praktek tanpa memerlukan perhatian dari programmer Java).
Tidak Ada Peluru Perak dalam pemrograman dan desain bahasa pemrograman (waspadai Masalah Pemutusan ; menjadi objek hidup yang bermanfaat tidak dapat diputuskan secara umum).
Anda mungkin juga membaca SICP , Memprogram Bahasa Pragmatik , Buku Naga , Cincang Dalam Potongan - potongan Kecil dan Sistem Operasi: Tiga Potong Mudah . Mereka bukan tentang Java, tetapi mereka akan membuka pikiran Anda dan harus membantu untuk memahami apa yang harus dilakukan JVM dan bagaimana cara kerjanya secara praktis (dengan bagian lain) di komputer Anda. Anda juga dapat menghabiskan waktu berbulan-bulan (atau beberapa tahun) untuk mempelajari kode sumber kompleks dari implementasi JVM open source yang ada (seperti OpenJDK , yang memiliki beberapa juta baris kode sumber).
sumber
finalize
manajemen sumber daya apa pun (dari filehandles, koneksi db, sumber daya gpu, dll.).Itu tidak benar - Java memang memiliki referensi lemah dan lunak, meskipun ini diterapkan pada tingkat objek daripada sebagai kata kunci bahasa.
Itu juga belum tentu benar - beberapa versi Objective C memang menggunakan pengumpul sampah generasi. Versi lain tidak memiliki pengumpulan sampah sama sekali.
Memang benar bahwa versi yang lebih baru dari Objective C menggunakan penghitungan referensi otomatis (ARC) daripada GC berbasis jejak, dan ini (sering) menghasilkan objek yang "dihapus" ketika penghitungan referensi itu mencapai nol. Namun, perhatikan bahwa implementasi JVM juga bisa memenuhi syarat dan bekerja dengan cara ini (heck, bisa sesuai dan tidak memiliki GC sama sekali.)
Jadi mengapa sebagian besar implementasi JVM tidak melakukan ini, dan alih-alih menggunakan algoritme GC berbasis jejak?
Sederhananya, ARC tidak utopis seperti yang terlihat pertama kali:
ARC memang memiliki kelebihan tentu saja - mudah diterapkan dan pengumpulannya bersifat deterministik. Namun kelemahan di atas, antara lain, adalah alasan mayoritas implementasi JVM akan menggunakan GC berbasis generasi.
sumber
Java tidak menentukan secara pasti kapan objek dikumpulkan karena itu memberikan implementasi kebebasan untuk memilih bagaimana menangani pengumpulan sampah.
Ada banyak mekanisme pengumpulan sampah yang berbeda, tetapi mekanisme yang menjamin Anda dapat mengumpulkan objek dengan segera hampir seluruhnya didasarkan pada penghitungan referensi (saya tidak mengetahui adanya algoritma yang mematahkan tren ini). Penghitungan referensi adalah alat yang ampuh, tetapi harus dibayar dengan mempertahankan jumlah referensi. Dalam kode singlethreaded, itu tidak lebih dari kenaikan dan penurunan, sehingga penunjuk pointer dapat biaya biaya pada urutan 3x sebanyak dalam kode terhitung referensi daripada dalam kode dihitung non-referensi (jika kompiler dapat memanggang semuanya ke mesin kode).
Dalam kode multithreaded, biayanya lebih tinggi. Itu baik panggilan untuk kenaikan / penurunan atom atau kunci, yang keduanya bisa mahal. Pada prosesor modern, operasi atom bisa 20x lebih mahal daripada operasi register sederhana (jelas bervariasi dari prosesor ke prosesor). Ini dapat meningkatkan biaya.
Jadi dengan ini, kita dapat mempertimbangkan pengorbanan yang dibuat oleh beberapa model.
Objective-C berfokus pada ARC - penghitungan referensi otomatis. Pendekatan mereka adalah menggunakan penghitungan referensi untuk semuanya. Tidak ada deteksi siklus (yang saya tahu), sehingga programmer diharapkan untuk mencegah siklus terjadi, yang memakan waktu pengembangan. Teorinya adalah bahwa pointer tidak ditugaskan terlalu sering, dan kompiler mereka dapat mengidentifikasi situasi di mana jumlah referensi bertambah / berkurang tidak dapat menyebabkan objek mati, dan mengeliminasi kenaikan / penurunan tersebut sepenuhnya. Dengan demikian mereka meminimalkan biaya penghitungan referensi.
CPython menggunakan mekanisme hybrid. Mereka menggunakan jumlah referensi, tetapi mereka juga memiliki pengumpul sampah yang mengidentifikasi siklus dan melepaskan mereka. Ini memberikan manfaat dari kedua dunia, dengan mengorbankan kedua pendekatan. CPython harus mempertahankan jumlah referensi danlakukan pembukuan untuk mendeteksi siklus. CPython lolos dengan ini dalam dua cara. Kepalannya adalah bahwa CPython benar-benar tidak sepenuhnya multithreaded. Ini memiliki kunci yang dikenal sebagai GIL yang membatasi multithreading. Ini berarti CPython dapat menggunakan kenaikan / penurunan normal daripada yang atomik, yang jauh lebih cepat. CPython juga ditafsirkan, yang berarti operasi seperti penugasan ke variabel sudah mengambil beberapa instruksi daripada hanya 1. Biaya tambahan untuk melakukan penambahan / pengurangan, yang dilakukan dengan cepat dalam kode C, kurang menjadi masalah karena kami sudah membayar biaya ini.
Java menurunkan pendekatan tidak menjamin sistem terhitung referensi sama sekali. Memang spesifikasinya tidak mengatakan apa - apa tentang bagaimana objek dikelola selain bahwa akan ada sistem manajemen penyimpanan otomatis. Namun, spesifikasi juga sangat mengisyaratkan asumsi bahwa ini akan menjadi sampah yang dikumpulkan dengan cara yang menangani siklus. Dengan tidak menentukan kapan objek kedaluwarsa, java mendapatkan kebebasan untuk menggunakan kolektor yang tidak membuang waktu bertambah / berkurang. Memang, algortihms pintar seperti pengumpul sampah generasi bahkan dapat menangani banyak kasus sederhana tanpa melihat data yang sedang direklamasi (mereka hanya perlu melihat data yang masih dirujuk).
Jadi kita bisa melihat masing-masing dari ketiganya harus melakukan tradeoff. Pengorbanan mana yang terbaik sangat tergantung pada sifat bagaimana bahasa itu dimaksudkan untuk digunakan.
sumber
Meskipun
finalize
didukung piggy ke GC Jawa, pengumpulan sampah pada intinya tidak tertarik pada benda mati, tetapi yang hidup. Pada beberapa sistem GC (mungkin termasuk beberapa implementasi Java), satu-satunya hal yang membedakan sekelompok bit yang mewakili objek dari sekelompok penyimpanan yang tidak digunakan untuk apa pun mungkin adalah adanya referensi ke yang sebelumnya. Sementara objek dengan finalizer ditambahkan ke daftar khusus, objek lain mungkin tidak memiliki apa pun di alam semesta yang mengatakan penyimpanannya dikaitkan dengan objek kecuali untuk referensi yang disimpan dalam kode pengguna. Ketika referensi seperti terakhir ditimpa, pola bit dalam memori akan segera berhenti dikenali sebagai objek, apakah ada sesuatu di alam semesta yang menyadarinya atau tidak.Tujuan pengumpulan sampah bukan untuk menghancurkan objek yang tidak memiliki referensi, tetapi untuk mencapai tiga hal:
Validasi referensi yang lemah yang mengidentifikasi objek yang tidak memiliki referensi yang sangat terjangkau yang terkait dengannya.
Cari daftar objek sistem dengan finalizer untuk melihat apakah ada di antara mereka yang tidak memiliki referensi yang sangat terjangkau yang terkait dengannya.
Identifikasi dan konsolidasi wilayah penyimpanan yang tidak digunakan oleh objek apa pun.
Perhatikan bahwa tujuan utama GC adalah # 3, dan semakin lama menunggu sebelum melakukannya, semakin banyak peluang untuk konsolidasi yang mungkin dimiliki. Masuk akal untuk melakukan # 3 dalam kasus di mana seseorang akan segera menggunakan untuk penyimpanan, tetapi sebaliknya lebih masuk akal untuk menunda itu.
sumber
Izinkan saya menyarankan penulisan ulang dan generalisasi pertanyaan Anda:
Dengan mengingat hal itu, gulir cepat jawabannya di sini. Ada tujuh sejauh ini (tidak termasuk yang satu ini), dengan beberapa utas komentar.
Itu jawaban kamu.
GC sulit. Ada banyak pertimbangan, banyak pertukaran yang berbeda, dan, pada akhirnya, banyak pendekatan yang sangat berbeda. Beberapa pendekatan tersebut memungkinkan untuk GC suatu objek segera setelah itu tidak diperlukan; yang lain tidak. Dengan menjaga kontrak tetap longgar, Java memberi pelaksana lebih banyak opsi.
Ada tradeoff bahkan dalam keputusan itu, tentu saja: dengan menjaga kontrak tetap longgar, sebagian besar Java * menghilangkan kemampuan programmer untuk bergantung pada destruktor. Ini adalah sesuatu yang sering dilewatkan oleh programmer C ++ ([rujukan?];)), Jadi itu bukan tradeoff yang tidak signifikan. Saya belum melihat diskusi tentang meta-keputusan tertentu, tetapi mungkin orang-orang Jawa memutuskan bahwa manfaat memiliki lebih banyak opsi GC melebihi manfaat untuk memberitahu programmer tepat ketika suatu objek akan dihancurkan.
* Ada
finalize
metode, tetapi karena berbagai alasan yang di luar cakupan untuk jawaban ini, sulit dan bukan ide yang baik untuk mengandalkannya.sumber
Ada dua strategi berbeda dalam menangani memori tanpa kode eksplisit yang ditulis oleh pengembang: Pengumpulan sampah, dan penghitungan referensi.
Pengumpulan sampah memiliki keuntungan bahwa ia "berfungsi" kecuali jika pengembang melakukan sesuatu yang bodoh. Dengan penghitungan referensi, Anda dapat memiliki siklus referensi, yang berarti bahwa itu "berfungsi" tetapi pengembang terkadang harus pintar. Jadi itu nilai tambah untuk pengumpulan sampah.
Dengan penghitungan referensi, objek akan hilang segera ketika penghitungan referensi turun ke nol. Itu keuntungan untuk penghitungan referensi.
Secara cepat, pengumpulan sampah lebih cepat jika Anda yakin para penggemar pengumpulan sampah, dan penghitungan referensi lebih cepat jika Anda yakin para penggemar penghitungan referensi.
Hanya ada dua metode berbeda untuk mencapai tujuan yang sama, Java memilih satu metode, Objective-C memilih yang lain (dan menambahkan banyak dukungan kompiler untuk mengubahnya dari yang menyusahkan menjadi sesuatu yang sedikit bekerja untuk pengembang).
Mengubah Jawa dari pengumpulan sampah ke penghitungan referensi akan menjadi pekerjaan besar, karena banyak perubahan kode akan diperlukan.
Secara teori, Jawa bisa menerapkan campuran pengumpulan sampah dan penghitungan referensi: Jika penghitungan referensi adalah 0, maka objek tersebut tidak dapat dijangkau, tetapi tidak harus sebaliknya. Jadi, Anda dapat menyimpan jumlah referensi dan menghapus objek ketika jumlah referensi mereka nol (dan kemudian menjalankan pengumpulan sampah dari waktu ke waktu untuk menangkap objek dalam siklus referensi yang tidak terjangkau). Saya pikir dunia terbagi 50/50 pada orang yang berpikir bahwa menambahkan penghitungan referensi ke pengumpulan sampah adalah ide yang buruk, dan orang yang berpikir bahwa menambahkan pengumpulan sampah ke penghitungan referensi adalah ide yang buruk. Jadi ini tidak akan terjadi.
Jadi Java dapat menghapus objek dengan segera jika jumlah referensi mereka menjadi nol, dan menghapus objek dalam siklus yang tidak terjangkau kemudian. Tapi itu keputusan desain, dan Java memutuskan untuk tidak melakukannya.
sumber
Semua argumen kinerja lain dan diskusi tentang kesulitan memahami ketika tidak ada lagi referensi ke objek benar meskipun satu ide lain yang menurut saya layak disebutkan adalah bahwa setidaknya ada satu JVM (azul) yang menganggap sesuatu seperti ini karena mengimplementasikan paralel gc yang intinya memiliki thread vm yang terus-menerus memeriksa referensi untuk mencoba menghapusnya yang akan bertindak tidak sepenuhnya berbeda dari apa yang Anda bicarakan. Pada dasarnya ia akan terus melihat-lihat tumpukan dan mencoba untuk mendapatkan kembali memori yang tidak direferensikan. Ini memang menimbulkan biaya kinerja yang sangat sedikit tetapi pada dasarnya mengarah ke nol atau waktu GC sangat singkat. (Itu kecuali ukuran tumpukan terus-menerus melebihi sistem RAM dan kemudian Azul menjadi bingung dan kemudian ada naga)
TLDR Sesuatu seperti itu agak ada untuk JVM itu hanya jvm khusus dan memiliki kelemahan seperti kompromi teknik lainnya.
Penafian: Saya tidak memiliki ikatan dengan Azul kami hanya menggunakannya di pekerjaan sebelumnya.
sumber
Memaksimalkan throughput berkelanjutan atau meminimalkan latensi gc berada dalam ketegangan dinamis, yang mungkin merupakan alasan paling umum mengapa GC tidak terjadi segera. Di beberapa sistem, seperti 911 aplikasi darurat, tidak memenuhi ambang latensi tertentu dapat mulai memicu proses kegagalan situs. Di tempat lain, seperti situs perbankan dan / atau arbitrase, jauh lebih penting untuk memaksimalkan throughput.
sumber
Kecepatan
Mengapa semua ini terjadi pada akhirnya karena kecepatan. Jika prosesor sangat cepat, atau (praktis) dekat dengannya, mis. 1.000.000.000.000.000.000.000.000.000.000.000.000.000.000 per detik maka Anda dapat memiliki hal-hal yang sangat panjang dan rumit terjadi di antara masing-masing operator, seperti memastikan objek yang dirujuk dihapus. Karena jumlah operasi per detik saat ini tidak benar dan, karena sebagian besar jawaban lain menjelaskan itu sebenarnya rumit dan sumber daya intensif untuk mencari tahu ini, pengumpulan sampah ada sehingga program dapat fokus pada apa yang sebenarnya mereka capai dalam suatu cara cepat.
sumber