Kapan ide yang baik untuk memaksa pengumpulan sampah?


135

Jadi saya membaca pertanyaan tentang memaksa pengumpul sampah C # untuk menjalankan di mana hampir setiap jawaban sama: Anda dapat melakukannya, tetapi Anda tidak boleh - kecuali untuk beberapa kasus yang sangat jarang . Sayangnya, tidak ada seorang pun di sana yang menjelaskan kasus-kasus seperti itu.

Bisakah Anda memberi tahu saya dalam skenario seperti apa sebenarnya ide yang baik atau masuk akal untuk memaksa pengumpulan sampah?

Saya tidak meminta kasus khusus C # melainkan semua bahasa pemrograman yang memiliki pemulung. Saya tahu Anda tidak bisa memaksakan GC pada semua bahasa, seperti Java, tapi anggaplah Anda bisa.


17
"melainkan, semua bahasa pemrograman yang memiliki pemulung" Bahasa yang berbeda (atau, lebih tepatnya, implementasi yang berbeda ) menggunakan metode yang berbeda untuk pengumpulan sampah, jadi Anda tidak mungkin menemukan aturan satu ukuran untuk semua.
Kolonel Thirty Two

4
@Doval Jika Anda berada di bawah batasan waktu nyata dan GC tidak memberikan jaminan yang cocok, Anda berada di antara yang sulit dan sulit. Mungkin mengurangi jeda yang tidak diinginkan vs tidak melakukan apa-apa, tetapi dari apa yang saya dengar itu "lebih mudah" untuk menghindari alokasi dalam operasi normal.

3
Saya mendapat kesan bahwa jika Anda mengharapkan untuk memiliki tenggat waktu nyata yang sulit, Anda tidak akan pernah menggunakan bahasa GC di tempat pertama.
GregRos

4
Saya tidak dapat melihat bagaimana Anda dapat menjawab ini dengan cara yang tidak spesifik untuk VM. Relevan untuk proses 32-bit, tidak relevan untuk proses 64-bit. .NET JVM dan untuk yang high-end
rwong

3
@ Davidvidon Anda bisa memaksanya dalam C #. Karena itu pertanyaannya.
Omega

Jawaban:


127

Anda benar-benar tidak dapat membuat pernyataan menyeluruh tentang cara yang tepat untuk menggunakan semua implementasi GC. Mereka sangat bervariasi. Jadi saya akan berbicara dengan .NET yang awalnya Anda sebut.

Anda harus tahu perilaku GC cukup dekat untuk melakukan ini dengan logika atau alasan apa pun.

Satu-satunya saran tentang koleksi yang dapat saya berikan adalah: Jangan pernah melakukannya.

Jika Anda benar-benar mengetahui detail rumit dari GC, Anda tidak akan memerlukan saran saya sehingga itu tidak masalah. Jika Anda belum tahu dengan keyakinan 100% itu akan membantu, dan harus mencari online dan menemukan jawaban seperti ini: Anda tidak boleh menelepon GC.Collect , atau alternatifnya: Anda harus mempelajari rincian tentang cara kerja GC dalam dan luar, dan hanya dengan begitu Anda akan tahu jawabannya .

Ada satu tempat aman yang masuk akal untuk menggunakan GC.Collect :

GC.Collect adalah API yang tersedia yang dapat Anda gunakan untuk membuat profil waktu untuk berbagai hal. Anda dapat membuat profil satu algoritma, mengumpulkan, dan membuat profil algoritma lain segera setelah mengetahui GC dari algo pertama tidak terjadi selama yang kedua Anda mengacaukan hasilnya.

Jenis profil seperti ini adalah satu-satunya waktu yang saya sarankan untuk mengumpulkan secara manual kepada siapa pun.


Contoh Buatlah

Salah satu use case yang mungkin adalah jika Anda memuat hal-hal yang sangat besar, mereka akan berakhir di Large Object Heap yang akan langsung menuju Gen 2, meskipun sekali lagi Gen 2 adalah untuk objek yang berumur panjang karena ia mengumpulkan lebih jarang. Jika Anda tahu bahwa Anda memuat objek yang berumur pendek ke Gen 2 dengan alasan apa pun, Anda bisa menghapusnya lebih cepat untuk membuat Gen 2 Anda lebih kecil dan koleksinya lebih cepat.

Ini adalah contoh terbaik yang bisa saya buat, dan itu tidak baik - tekanan LOH yang Anda bangun di sini akan menyebabkan koleksi lebih sering, dan koleksi sangat sering terjadi - kemungkinan besar itu akan menghapus LOH sama seperti secepat Anda meniupnya dengan benda-benda sementara. Saya hanya tidak percaya diri untuk menganggap frekuensi pengumpulan yang lebih baik daripada GC itu sendiri - disetel oleh orang-orang yang jauh lebih pintar dari saya.


Jadi mari kita bicara tentang beberapa semantik dan mekanisme dalam. NET GC ... atau ..

Semua yang saya pikir saya tahu tentang .NET GC

Tolong, siapa pun yang menemukan kesalahan di sini - perbaiki saya. Sebagian besar GC dikenal sebagai ilmu hitam dan sementara saya mencoba untuk mengabaikan detail yang saya tidak yakin, saya mungkin masih memiliki beberapa hal yang salah.

Di bawah ini sengaja hilang banyak detail yang tidak saya yakini, dan juga kumpulan informasi yang jauh lebih besar yang tidak saya sadari. Gunakan informasi ini dengan risiko Anda sendiri.


Konsep GC

.NET GC terjadi pada waktu yang tidak konsisten, itulah sebabnya ini disebut "non-deterministik", ini berarti Anda tidak dapat bergantung padanya untuk terjadi pada waktu tertentu. Ini juga merupakan pengumpul sampah generasi, yang berarti memecah objek Anda menjadi berapa banyak GC yang telah dilaluinya.

Objek dalam tumpukan 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 telah disimpan dalam 0 koleksi, ini baru saja dibuat sehingga baru-baru ini tidak ada koleksi yang terjadi sejak instantiasi mereka. Objek di tumpukan 1 Gen Anda telah hidup melalui satu pass pengumpulan, dan juga objek di tumpukan 2 Gen Anda telah hidup melalui 2 pass pengumpulan.

Sekarang perlu dicatat alasan mengapa ini memenuhi syarat untuk generasi dan partisi khusus ini. .NET GC hanya mengenali tiga generasi ini, karena lintasan pengumpulan yang melewati tiga tumpukan ini semuanya sedikit berbeda. Beberapa benda dapat bertahan koleksi melewati ribuan kali. GC hanya meninggalkan ini di sisi lain dari partisi tumpukan Gen 2, tidak ada gunanya mempartisi mereka lebih jauh karena sebenarnya Gen 44; pengumpulan koleksi pada mereka sama dengan semua yang ada di tumpukan 2 Gen.

Ada tujuan semantik untuk generasi spesifik ini, serta mekanisme yang diterapkan yang menghormati ini, dan saya akan membahasnya sebentar lagi.


Apa yang ada di koleksi

Konsep dasar pass pengumpulan GC adalah bahwa ia memeriksa setiap objek di ruang tumpukan untuk melihat apakah masih ada referensi langsung (akar GC) untuk objek-objek ini. Jika root GC ditemukan untuk suatu objek, itu berarti saat ini mengeksekusi kode masih dapat mencapai dan menggunakan objek itu, sehingga tidak dapat dihapus. Namun jika root GC tidak ditemukan untuk suatu objek, itu berarti proses berjalan tidak lagi membutuhkan objek, sehingga dapat menghapusnya untuk membebaskan memori untuk objek baru.

Sekarang setelah selesai membersihkan banyak objek dan meninggalkan beberapa sendirian, akan ada efek samping yang tidak menguntungkan: Kesenjangan ruang bebas antara objek hidup di mana yang mati dihilangkan. Fragmentasi memori ini jika dibiarkan sendiri hanya akan membuang-buang memori, jadi koleksi biasanya akan melakukan apa yang disebut "pemadatan" di mana mereka mengambil semua objek hidup yang tersisa dan meremasnya bersama di tumpukan sehingga memori bebas berdekatan pada satu sisi tumpukan untuk Gen 0.

Sekarang diberi ide 3 tumpukan memori, semua dipartisi dengan jumlah pass koleksi yang telah mereka lalui, mari kita bicara tentang mengapa partisi ini ada.


Koleksi Gen 0

Gen 0 menjadi objek terbaru yang absolut, cenderung sangat kecil - sehingga Anda dapat dengan aman mengumpulkannya dengan sering . Frekuensi memastikan tumpukan tetap kecil dan koleksi sangat cepat karena mereka mengumpulkan lebih dari tumpukan kecil. Ini didasarkan lebih atau kurang pada heuristik yang mengklaim: Sebagian besar objek sementara yang Anda buat, sangat sementara, sehingga sementara mereka tidak akan lagi digunakan atau dirujuk segera setelah digunakan, dan dengan demikian dapat dikumpulkan.


Koleksi Gen 1

Gen 1 adalah objek yang tidak termasuk dalam kategori objek yang sangat sementara ini, mungkin masih berumur pendek, karena sebagian besar objek yang dibuat tidak digunakan dalam waktu lama. Karena itu, Gen 1 mengumpulkan lebih sering juga, sekali lagi menjaga tumpukannya kecil sehingga koleksinya cepat. Namun anggapannya kurang dari objek itu sementara dari Gen 0, sehingga mengumpulkan lebih jarang dari Gen 0

Saya akan mengatakan saya terus terang tidak tahu mekanisme teknis yang berbeda antara pass pengumpulan Gen 0 dan Gen 1, jika ada sama sekali selain frekuensi yang mereka kumpulkan.


Koleksi Gen 2

Gen 2 sekarang harus menjadi ibu dari semua tumpukan kan? Ya, itu kurang lebih benar. Di sinilah semua objek permanen Anda hidup - objek Main()hidup Anda misalnya, dan segala sesuatu yang Main()referensi karena mereka akan di-root sampai Anda Main()kembali pada akhir proses Anda.

Mengingat bahwa Gen 2 adalah ember untuk segala sesuatu yang pada dasarnya tidak dapat dikumpulkan oleh generasi lain, objeknya sebagian besar permanen, atau setidaknya berumur panjang. Jadi mengakui sangat sedikit dari apa yang ada dalam Gen 2 sebenarnya akan menjadi sesuatu yang dapat dikumpulkan, itu tidak perlu sering dikumpulkan. Ini memungkinkan koleksinya juga menjadi lebih lambat, karena ia mengeksekusi jauh lebih jarang. Jadi ini pada dasarnya di mana mereka telah menempel pada semua perilaku ekstra untuk skenario aneh, karena mereka punya waktu untuk mengeksekusinya.


Tumpukan Objek Besar

Salah satu contoh perilaku ekstra Gen 2 adalah bahwa ia juga melakukan pengumpulan pada Tumpukan Objek Besar. Sampai sekarang saya telah berbicara sepenuhnya tentang Small Object Heap, tetapi .NET runtime mengalokasikan hal-hal dari ukuran tertentu ke heap terpisah karena apa yang saya sebut sebagai pemadatan di atas. Pemadatan membutuhkan benda bergerak di sekitar saat koleksi selesai pada Tumpukan Benda Kecil. Jika ada objek 10MB yang hidup di Gen 1, itu akan memakan waktu lebih lama untuk menyelesaikan pemadatan setelah pengumpulan, sehingga memperlambat koleksi Gen 1. Sehingga objek 10MB dialokasikan untuk Tumpukan Objek Besar, dan dikumpulkan selama Gen 2 yang berjalan sangat jarang.


Finalisasi

Contoh lain adalah objek dengan finalizer. Anda meletakkan finalizer pada objek yang referensi sumber daya di luar ruang lingkup .NETs GC (sumber daya tidak dikelola). Finalizer adalah satu-satunya cara GC menuntut sumber daya yang tidak dikelola dikumpulkan - Anda menerapkan finalizer Anda untuk melakukan pengumpulan / penghapusan / pelepasan sumber daya yang tidak dikelola secara manual untuk memastikan tidak bocor dari proses Anda. Ketika GC mengeksekusi objek Anda finalizer, maka implementasi Anda akan menghapus sumber daya yang tidak dikelola, membuat GC mampu menghapus objek Anda tanpa risiko kebocoran sumber daya.

Mekanisme yang digunakan oleh para finalis ini adalah dengan direferensikan secara langsung dalam antrian finalisasi. Ketika runtime mengalokasikan objek dengan finalizer, itu menambahkan pointer ke objek itu ke antrian finalisasi, dan mengunci objek Anda di tempat (disebut pinning) sehingga pemadatan tidak akan memindahkannya yang akan mematahkan referensi antrian finalisasi. Ketika pass pengumpulan terjadi, akhirnya objek Anda akan ditemukan tidak lagi memiliki root GC, tetapi finalisasi harus dijalankan sebelum dapat dikumpulkan. Jadi, ketika objek mati, koleksi akan memindahkan referensi itu dari antrian finalisasi dan menempatkan referensi pada apa yang dikenal sebagai antrian "FReachable". Kemudian koleksi berlanjut. Di lain waktu "non-deterministik" di masa depan, utas terpisah yang dikenal sebagai utas Finalizer akan melewati antrian FReachable, mengeksekusi finalizer untuk setiap objek yang dirujuk. Setelah selesai, antrian FReachable kosong, dan telah membalik sedikit di header setiap objek yang mengatakan mereka tidak perlu finalisasi (Bit ini juga dapat dibalik secara manual denganGC.SuppressFinalizeyang umum dalam Dispose()metode), saya juga curiga itu telah melepaskan pin objek, tetapi jangan mengutip saya tentang itu. Koleksi berikutnya yang muncul di tumpukan apa pun objek ini, akhirnya akan mengumpulkannya. Koleksi Gen 0 bahkan tidak memperhatikan objek dengan bit finalisasi yang diperlukan, itu secara otomatis mempromosikannya, bahkan tanpa memeriksa root mereka. Objek tanpa akar yang membutuhkan finalisasi dalam Gen 1, akan dilemparkan pada FReachableantrian, tetapi koleksi tidak melakukan hal lain dengannya, sehingga ia hidup ke dalam Gen 2. Dengan cara ini, semua objek yang memiliki finalizer, dan tidak GC.SuppressFinalizeakan dikumpulkan dalam Gen 2.


4
@FlorianMargaine ya ... mengatakan sesuatu tentang "GC" di semua implementasi benar-benar tidak masuk akal ..
Jimmy Hoffa

10
tl; dr: Gunakan kolam objek sebagai gantinya.
Robert Harvey

5
tl; dr: Untuk penentuan waktu / pembuatan profil, ini dapat bermanfaat.
kutschkem

3
@Den setelah membaca deskripsi saya di atas tentang mekanika (seperti yang saya mengerti), apa manfaatnya seperti yang Anda lihat? Anda membersihkan sejumlah besar benda - di SOH (atau LOH?)? Apakah Anda hanya membuat utas lain berhenti untuk koleksi ini? Apakah koleksi itu hanya mempromosikan objek dua kali lebih banyak ke Gen 2 saat dibersihkan? Apakah koleksi menyebabkan pemadatan pada LOH (apakah Anda sudah dihidupkan?)? Berapa banyak tumpukan GC yang Anda miliki dan apakah GC Anda dalam mode server atau desktop? GC adalah gunung es, pengkhianatan berada di bawah air. Hanya menghindari. Saya tidak cukup pintar untuk mengumpulkan dengan nyaman.
Jimmy Hoffa

4
@RobertHarvey Object pools juga bukan peluru perak. Pengumpul sampah generasi 0 sudah efektif merupakan kumpulan objek - biasanya berukuran agar sesuai dengan tingkat cache terkecil dan dengan demikian objek baru umumnya dibuat dalam memori yang sudah ada dalam cache. Kumpulan objek Anda sekarang bersaing melawan pembibitan GC untuk mendapatkan cache, dan jika jumlah pembibitan GC dan kumpulan Anda lebih besar dari cache, Anda jelas akan kehilangan cache. Dan jika Anda berencana untuk menggunakan paralelisme sekarang Anda harus menerapkan kembali sinkronisasi dan khawatir tentang pembagian yang salah.
Doval

68

Sayangnya, tidak ada seorang pun di sana yang menjelaskan kasus-kasus seperti itu.

Saya akan memberikan beberapa contoh. Secara keseluruhan jarang bahwa memaksakan GC adalah ide yang bagus tetapi bisa sangat berharga. Jawaban ini dari pengalaman saya dengan literatur .NET dan GC. Ini harus digeneralisasikan dengan baik ke platform lain (setidaknya mereka yang memiliki GC signifikan).

  • Benchmark dari berbagai jenis. Anda ingin kondisi tumpukan terkelola yang dikenal saat benchmark dimulai sehingga GC tidak memicu secara acak selama benchmark. Ketika Anda mengulangi tolok ukur, Anda ingin angka dan jumlah GC yang sama berfungsi di setiap pengulangan.
  • Pelepasan sumber daya secara tiba-tiba. Misalnya menutup Jendela GUI yang signifikan atau menyegarkan cache (dan dengan demikian melepaskan konten cache lama yang berpotensi besar). GC tidak dapat mendeteksi ini karena semua yang Anda lakukan adalah menetapkan referensi ke nol. Fakta bahwa anak yatim ini grafik seluruh objek tidak mudah terdeteksi.
  • Melepaskan sumber daya yang tidak dikelola yang bocor . Ini seharusnya tidak pernah terjadi, tentu saja, tetapi saya telah melihat kasus di mana perpustakaan pihak ke-3 membocorkan hal-hal (seperti objek COM). Pengembang terkadang dipaksa untuk membuat koleksi.
  • Aplikasi interaktif seperti game . Selama bermain game memiliki anggaran waktu yang sangat ketat per frame (60Hz => 16ms per frame). Untuk menghindari hickup, Anda perlu strategi untuk berurusan dengan GC. Salah satu strategi tersebut adalah untuk menunda GC G2 sebanyak mungkin dan memaksanya pada waktu yang tepat seperti layar pemuatan atau adegan pemotongan. GC tidak bisa tahu kapan momen terbaik itu.
  • Kontrol latensi secara umum. Beberapa aplikasi web menonaktifkan GC dan secara berkala menjalankan koleksi G2 saat sedang tidak digerakkan rotasi penyeimbang beban. Dengan begitu latensi G2 tidak pernah muncul ke pengguna.

Jika tujuan Anda adalah throughput maka semakin jarang GC semakin baik. Dalam kasus-kasus itu, memaksa koleksi tidak dapat memiliki dampak positif (kecuali untuk masalah yang agak dibuat-buat seperti meningkatkan pemanfaatan cache CPU dengan menghapus objek mati yang diselingi di objek langsung). Pengumpulan batch lebih efisien untuk semua kolektor yang saya tahu. Untuk aplikasi produksi dalam konsumsi memori kondisi-mapan menginduksi GC tidak membantu.

Contoh-contoh yang diberikan di atas konsistensi target dan batasan penggunaan memori. Dalam kasus-kasus tersebut, GC yang diinduksi dapat masuk akal.

Tampaknya ada gagasan yang tersebar luas bahwa GC adalah entitas ilahi yang mendorong koleksi kapan pun memang optimal untuk melakukannya. Tidak ada GC yang saya tahu canggih dan memang sangat sulit untuk menjadi optimal untuk GC. GC tahu lebih sedikit daripada pengembang. Heuristiknya didasarkan pada penghitung memori dan hal-hal seperti tingkat pengumpulan dan sebagainya. Heuristik biasanya baik tetapi mereka tidak menangkap perubahan mendadak dalam perilaku aplikasi seperti melepaskan sejumlah besar memori yang dikelola. Ia juga buta terhadap sumber daya yang tidak dikelola dan persyaratan latensi.

Perhatikan, bahwa biaya GC bervariasi dengan ukuran tumpukan dan jumlah referensi pada tumpukan itu. Pada tumpukan kecil biayanya bisa sangat kecil. Saya telah melihat tingkat pengumpulan G2 dengan .NET 4.5 dari 1-2GB / detik pada aplikasi produksi dengan ukuran tumpukan 1GB.


Untuk kasus kontrol latensi, saya kira alih-alih melakukan ini secara berkala, Anda juga bisa melakukannya dengan kebutuhan (yaitu ketika penggunaan memori tumbuh pada ambang batas tertentu).
Paŭlo Ebermann

3
+1 untuk paragraf kedua hingga terakhir. Beberapa orang memiliki sentimen yang sama tentang kompiler dan cepat menyebut hampir semua "optimasi prematur". Saya biasanya memberi tahu mereka sesuatu yang serupa.
Honza Brabec

2
+1 untuk paragraf itu juga. Saya menemukan itu mengejutkan bahwa orang berpikir program komputer yang ditulis oleh orang lain tentu harus memahami karakteristik kinerja program mereka lebih baik daripada diri mereka sendiri.
Mehrdad

1
@HonzaBrabec Masalahnya sama dalam kedua kasus: Jika Anda pikir Anda lebih tahu daripada GC atau kompiler, maka sangat mudah untuk melukai diri sendiri. Jika Anda benar-benar tahu lebih banyak, maka Anda hanya mengoptimalkan ketika Anda tahu itu bukan prematur.
svick

27

Sebagai prinsip umum, seorang pemulung akan mengumpulkan ketika ia mengalami "tekanan memori", dan itu dianggap ide yang baik untuk tidak memilikinya mengumpulkan pada waktu lain karena Anda dapat menyebabkan masalah kinerja atau bahkan jeda yang terlihat dalam pelaksanaan program Anda. Dan pada kenyataannya, titik pertama tergantung pada yang kedua: untuk seorang pengumpul sampah generasi, setidaknya, ia berjalan lebih efisien semakin tinggi rasio sampah menjadi benda yang baik, sehingga untuk meminimalkan jumlah waktu yang dihabiskan untuk menghentikan sementara program , harus menunda-nunda dan membiarkan sampah menumpuk sebanyak mungkin.

Waktu yang tepat untuk secara manual memanggil pemulung, maka, adalah ketika Anda selesai melakukan sesuatu yang 1) kemungkinan telah menciptakan banyak sampah, dan 2) diharapkan oleh pengguna untuk mengambil waktu dan membiarkan sistem tidak responsif bagaimanapun. Contoh klasik adalah pada akhir memuat sesuatu yang besar (dokumen, model, level baru, dll.)


12

Satu hal yang belum pernah disebutkan adalah bahwa, meskipun Windows GC luar biasa bagus, GC di Xbox adalah sampah (pun intended) .

Jadi, ketika mengkodekan sebuah game XNA yang dimaksudkan untuk berjalan di XBox, itu sangat penting untuk pengumpulan sampah waktu untuk saat-saat yang tepat, atau Anda akan memiliki cegukan FPS berselang yang mengerikan. Selain itu, di XBox itu biasa menggunakan structcara, jauh lebih sering daripada biasanya, untuk meminimalkan jumlah objek yang perlu dikumpulkan sampah.


4

Pengumpulan sampah adalah alat manajemen memori yang pertama dan terutama. Dengan demikian, pengumpul sampah akan mengumpulkan ketika ada tekanan memori.

Pengumpul sampah modern sangat baik, dan menjadi lebih baik, sehingga tidak mungkin Anda dapat memperbaikinya dengan mengumpulkan secara manual. Bahkan jika Anda dapat meningkatkan hal-hal hari ini, mungkin saja peningkatan di masa depan untuk pengumpul sampah yang Anda pilih akan membuat optimasi Anda tidak efektif, atau bahkan kontraproduktif.

Namun , pengumpul sampah biasanya tidak berupaya mengoptimalkan penggunaan sumber daya selain memori. Dalam lingkungan pengumpulan sampah, sumber daya non-memori paling berharga memiliki closemetode atau yang serupa, tetapi ada beberapa kesempatan di mana hal ini tidak terjadi karena alasan tertentu, seperti kompatibilitas dengan API yang ada.

Dalam kasus ini, masuk akal untuk meminta pengumpulan sampah secara manual ketika Anda tahu bahwa sumber daya non-memori yang berharga sedang digunakan.

RMI

Salah satu contoh konkret dari ini adalah Doa Metode Jarak Jauh Java. RMI adalah pustaka panggilan prosedur jarak jauh. Anda biasanya memiliki server, yang membuat berbagai objek tersedia untuk digunakan oleh klien. Jika server tahu bahwa suatu objek tidak digunakan oleh klien mana pun, maka objek tersebut memenuhi syarat untuk pengumpulan sampah.

Namun, satu-satunya cara server mengetahui hal ini adalah jika klien mengatakannya, dan klien hanya memberi tahu server bahwa ia tidak membutuhkan objek lagi setelah klien mengumpulkan sampah apa pun yang menggunakannya.

Ini menimbulkan masalah, karena klien mungkin memiliki banyak memori bebas, jadi mungkin tidak menjalankan pengumpulan sampah sangat sering. Sementara itu, server mungkin memiliki banyak objek yang tidak digunakan dalam memori, yang tidak dapat dikumpulkan karena tidak tahu bahwa klien tidak menggunakannya.

Solusi dalam RMI adalah agar klien menjalankan pengumpulan sampah secara berkala, bahkan ketika memiliki banyak memori bebas, untuk memastikan bahwa objek dikumpulkan segera di server.


"Dalam kasus ini mungkin masuk akal untuk secara manual memanggil pengumpulan sampah ketika Anda tahu bahwa sumber daya non-memori yang berharga sedang digunakan" - jika sumber daya non-memori sedang digunakan Anda harus menggunakan usingblok atau memanggil Closemetode untuk memastikan sumber daya tersebut dibuang sesegera mungkin. Mengandalkan GC untuk membersihkan sumber daya non-memori tidak dapat diandalkan, dan menyebabkan semua jenis masalah (terutama dengan file yang perlu dikunci untuk akses sehingga hanya dapat dibuka sekali).
Jules

Dan seperti yang dinyatakan dalam jawaban, ketika closemetode tersedia (atau sumber daya dapat digunakan dengan usingblok), ini adalah pendekatan yang tepat. Jawabannya secara khusus berkaitan dengan kasus-kasus langka di mana mekanisme ini tidak tersedia.
James_pic

Pendapat pribadi saya adalah bahwa setiap antarmuka yang mengelola sumber daya non-memori tetapi tidak menyediakan metode yang dekat adalah antarmuka yang tidak boleh digunakan , karena tidak ada cara untuk menggunakannya dengan andal.
Jules

@ Jules saya setuju, tapi kadang-kadang itu tidak bisa dihindari. Terkadang abstraksi bocor, dan menggunakan abstraksi bocor lebih baik daripada tidak menggunakan abstraksi. Terkadang Anda perlu bekerja dengan kode lawas yang menuntut Anda membuat janji yang Anda tahu tidak bisa Anda pertahankan. Ya, ini jarang, dan harus dihindari jika memungkinkan, dan ada alasan bahwa ada semua peringatan ini yang memaksa pengumpulan sampah, tetapi situasi ini memang muncul, dan OP bertanya seperti apa situasi ini - yang saya jawab .
James_pic

2

Praktik terbaik adalah tidak memaksa pengumpulan sampah dalam banyak kasus. (Setiap sistem yang saya kerjakan yang memiliki pengumpulan sampah paksa, menggarisbawahi masalah yang jika diselesaikan akan menghilangkan kebutuhan untuk memaksa pengumpulan sampah, dan mempercepat sistem dengan sangat.)

Ada beberapa kasus ketika Anda tahu lebih banyak tentang penggunaan memori maka pengumpul sampah tidak. Ini tidak mungkin benar dalam aplikasi multi-pengguna, atau layanan yang merespons lebih dari satu permintaan sekaligus.

Namun dalam beberapa jenis pemrosesan batch Anda tahu lebih banyak daripada GC. Misalnya pertimbangkan aplikasi itu.

  • Diberikan daftar nama file di baris perintah
  • Memproses satu file kemudian menulis hasilnya ke file hasil.
  • Saat memproses file, membuat banyak objek yang saling terkait yang tidak dapat dikumpulkan sampai pemrosesan file selesai (mis. Pohon parse)
  • Tidak mempertahankan status kecocokan antara file yang telah diproses .

Anda mungkin dapat membuat case (setelah hati-hati) menguji bahwa Anda harus memaksa pengumpulan sampah penuh setelah Anda memproses setiap file.

Kasing lain adalah layanan yang bangun setiap beberapa menit untuk memproses beberapa barang, dan tidak membuat keadaan apa pun saat tertidur . Maka, memaksakan koleksi penuh sebelum tidur mungkin bermanfaat.

Satu-satunya waktu saya akan mempertimbangkan untuk memaksa koleksi adalah ketika saya tahu bahwa banyak objek telah dibuat baru-baru ini dan sangat sedikit objek yang saat ini direferensikan.

Saya lebih suka memiliki API pengumpulan sampah ketika saya bisa memberikan petunjuk tentang hal semacam ini tanpa harus memaksakan GC diri saya.

Lihat juga " Tidbits Kinerja Rico Mariani "


2

Ada beberapa kasus di mana Anda mungkin ingin memanggil gc () sendiri.

  • [ Beberapa orang mengatakan bahwa ini tidak baik karena dapat mempromosikan objek ke ruang generasi yang lebih tua yang saya setuju bukan hal yang baik. Namun, TIDAK selalu benar bahwa akan selalu ada objek yang dapat dipromosikan. Sangat mungkin bahwa setelah gc()panggilan ini , sangat sedikit objek tetap apalagi dipindahkan ke ruang generasi yang lebih tua ] Ketika Anda akan membuat koleksi besar objek dan menggunakan banyak memori. Anda hanya ingin membersihkan ruang sebanyak mungkin persiapan. Ini hanya akal sehat. Dengan menelepon gc()secara manual, tidak akan ada grafik rujukan cek berlebihan pada bagian dari koleksi besar objek yang Anda muat ke dalam memori. Singkatnya, jika Anda menjalankan gc()sebelum Anda memuat banyak ke dalam memori,gc() diinduksi selama beban terjadi kurang dari setidaknya satu kali ketika memuat mulai membuat tekanan memori.
  • Setelah selesai memuat koleksi besarbesarobjek dan Anda tidak akan memuat lebih banyak objek ke dalam memori. Singkatnya, Anda beralih dari membuat fase ke menggunakan fase. Dengan memanggil gc()tergantung pada implementasi, memori yang digunakan akan dipadatkan yang secara besar-besaran meningkatkan lokalitas cache. Ini akan menghasilkan peningkatan kinerja besar-besaran yang tidak akan Anda dapatkan dari profil .
  • Mirip dengan yang pertama, tetapi dari pandangan bahwa jika Anda melakukannya gc()dan implementasi manajemen memori mendukung, Anda akan menciptakan kesinambungan yang lebih baik untuk memori fisik Anda. Ini lagi membuat koleksi besar objek baru lebih kontinu dan kompak yang pada gilirannya meningkatkan kinerja

1
Bisakah seseorang menunjukkan alasan downvote? Saya sendiri tidak cukup tahu untuk menilai jawabannya (sekilas itu masuk akal bagi saya).
Omega

1
Saya kira Anda mendapat downvote untuk poin ketiga. Berpotensi juga untuk mengatakan "Ini hanya akal sehat".
immibis

2
Saat Anda membuat koleksi besar objek, GC harus cukup pintar untuk mengetahui apakah koleksi dibutuhkan. Sama ketika memori perlu dipadatkan. Mengandalkan GC untuk mengoptimalkan memori lokalitas objek terkait sepertinya tidak dapat diterapkan. Saya pikir Anda dapat menemukan solusi lain (struct, tidak aman, ...). (Saya bukan downvoter).
Guillaume

3
Gagasan pertama Anda tentang waktu yang baik adalah saran yang buruk menurut saya. Peluangnya tinggi karena telah ada koleksi baru-baru ini sehingga upaya Anda mengumpulkan lagi hanya akan secara sewenang-wenang mempromosikan objek ke generasi selanjutnya, yang hampir selalu buruk. Generasi selanjutnya memiliki koleksi yang membutuhkan waktu lebih lama, meningkatkan ukuran timbunan mereka "untuk membersihkan ruang sebanyak mungkin" hanya menyebabkan ini menjadi lebih bermasalah. Ditambah lagi jika Anda akan meningkatkan tekanan memori dengan sebuah beban, Anda kemungkinan akan mulai mendorong koleksi, yang akan berjalan lebih lambat karena peningkatan Gen1 / 2
Jimmy Hoffa

2
By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.Jika Anda mengalokasikan banyak objek dalam odds baris, mereka sudah dipadatkan. Jika ada, pengumpulan sampah mungkin mengocoknya sedikit. Either way, menggunakan struktur data yang padat dan tidak melompat-lompat secara acak dalam memori akan memiliki dampak yang lebih besar. Jika Anda menggunakan daftar tertaut satu-elemen-per-simpul yang naif, tidak ada jumlah tipu daya GC manual yang dapat menebusnya.
Doval

2

Contoh dunia nyata:

Saya memiliki aplikasi web yang menggunakan sekumpulan data yang sangat besar yang jarang berubah dan yang perlu diakses dengan sangat cepat (cukup cepat untuk respons per-keystroke melalui AJAX).

Hal yang cukup jelas untuk dilakukan di sini adalah memuat grafik yang relevan ke dalam memori, dan mengaksesnya dari sana daripada database, memperbarui grafik ketika DB berubah.

Tapi karena sangat besar, beban naif akan menghabiskan setidaknya 6GB memori dengan data yang akan tumbuh di masa depan. (Saya tidak memiliki angka pasti, setelah itu jelas bahwa mesin 2GB saya mencoba untuk mengatasi setidaknya 6GB saya memiliki semua pengukuran yang saya perlu tahu itu tidak akan berfungsi).

Untungnya, ada sejumlah besar objek yang tidak dapat diubah dalam kumpulan data ini yang sama satu sama lain; begitu saya mengetahui bahwa batch tertentu sama dengan batch lain, saya bisa menambahkan satu referensi ke yang lain sehingga memungkinkan banyak data untuk dikumpulkan dan karenanya memasukkan semuanya menjadi kurang dari setengah pertunjukan.

Semua baik dan bagus, tetapi untuk ini masih berputar melalui lebih dari 6GB objek dalam waktu sekitar setengah menit untuk sampai ke keadaan ini. Dibiarkan sendiri, GC tidak mengatasinya; lonjakan aktivitas di atas pola aplikasi yang biasa (jauh lebih sedikit pada deallocations per detik) terlalu tajam.

Jadi secara berkala memanggil GC.Collect()selama proses pembangunan ini berarti bahwa semuanya bekerja dengan lancar. Tentu saja, saya tidak secara manual memanggil GC.Collect()sisa waktu aplikasi berjalan.

Kasus dunia nyata ini adalah contoh yang baik dari pedoman kapan kita harus menggunakan GC.Collect():

  1. Gunakan dengan kasus yang relatif jarang ada banyak objek yang dibuat tersedia untuk koleksi (layak megabita tersedia, dan pembuatan grafik ini adalah kasus yang sangat langka selama masa aplikasi (sekitar satu menit per minggu).
  2. Lakukan ketika kehilangan kinerja relatif dapat ditoleransi; ini hanya terjadi pada saat permulaan aplikasi. (Contoh bagus lain dari aturan ini adalah antara level selama pertandingan, atau poin lain dalam permainan di mana pemain tidak akan kecewa dengan sedikit jeda).
  3. Profil untuk memastikan memang ada perbaikan. (Cukup mudah; "Berhasil" hampir selalu berdetak "tidak berhasil").

Sebagian besar waktu ketika saya berpikir saya mungkin memiliki kasus di mana GC.Collect()layak disebut, karena poin 1 dan 2 diterapkan, poin 3 menyarankan itu membuat segalanya menjadi lebih buruk atau setidaknya membuat segalanya tidak lebih baik (dan dengan sedikit atau tanpa perbaikan saya akan condong ke arah tidak memanggil panggil sebagai pendekatan yang lebih mungkin untuk membuktikan lebih baik selama aplikasi seumur hidup).


0

Saya memiliki penggunaan untuk pembuangan sampah yang agak tidak lazim.

Ada praktik salah arah ini yang sayangnya sangat lazim di dunia C #, menerapkan pembuangan objek menggunakan idiom yang jelek, kikuk, tidak elegan, dan rentan kesalahan yang dikenal sebagai IDisposable-disposing . MSDN menjelaskan panjang lebar , dan banyak orang bersumpah, mengikutinya dengan religius, menghabiskan berjam-jam mendiskusikan dengan tepat bagaimana hal itu harus dilakukan, dll.

(Harap dicatat bahwa apa yang saya sebut jelek di sini bukanlah pola pembuangan objek itu sendiri; apa yang saya sebut jelek adalah IDisposable.Dispose( bool disposing )idiom tertentu .)

Ungkapan ini diciptakan karena seharusnya tidak mungkin untuk menjamin bahwa penghancur objek Anda akan selalu dipanggil oleh pengumpul sampah untuk membersihkan sumber daya, sehingga orang melakukan pembersihan sumber daya di dalam IDisposable.Dispose(), dan jika mereka lupa, mereka juga mencobanya sekali lagi dari dalam destruktor. Anda tahu, untuk berjaga-jaga.

Tapi kemudian Anda IDisposable.Dispose()mungkin memiliki objek yang dikelola dan tidak dikelola untuk dibersihkan, tetapi yang dikelola tidak dapat dibersihkan ketika IDisposable.Dispose()dipanggil dari dalam destruktor, karena mereka telah dirawat oleh pengumpul sampah pada saat itu, jadi ada Apakah ini kebutuhan untuk Dispose()metode terpisah yang menerima bool disposingbendera untuk mengetahui apakah kedua objek yang dikelola dan tidak dikelola harus dibersihkan, atau hanya yang tidak dikelola.

Maaf, tapi ini gila.

Saya mengikuti aksioma Einstein, yang mengatakan bahwa segala sesuatunya harus sesederhana mungkin, tetapi tidak sesederhana itu. Jelas, kita tidak bisa menghilangkan pembersihan sumber daya, jadi solusi paling sederhana yang mungkin harus memasukkan setidaknya itu. Solusi paling sederhana berikutnya adalah selalu membuang segala sesuatu pada waktu yang tepat yang seharusnya dibuang, tanpa menyulitkan hal-hal dengan mengandalkan destructor sebagai alternatif untuk mundur.

Sekarang, sebenarnya, tidak mungkin untuk menjamin bahwa tidak ada programmer yang akan membuat kesalahan dengan lupa memanggil IDisposable.Dispose(), tetapi yang bisa kita lakukan adalah menggunakan destructor untuk menangkap kesalahan ini. Ini sangat sederhana, sungguh: yang harus dilakukan destruktor hanyalah menghasilkan entri log jika mendeteksi bahwa disposedbendera objek sekali pakai tidak pernah disetel ke true. Jadi, penggunaan destruktor bukan bagian integral dari strategi pembuangan kami, tetapi itu adalah mekanisme jaminan kualitas kami. Dan karena ini adalah hanya mode debug-mode, kita dapat menempatkan seluruh destruktor kita di dalam sebuah #if DEBUGblok, jadi kita tidak pernah dikenakan hukuman penghancuran dalam lingkungan produksi. ( IDisposable.Dispose( bool disposing )Ungkapan itu mengatur ituGC.SuppressFinalize() harus dipanggil secara tepat untuk mengurangi overhead finalisasi, tetapi dengan mekanisme saya adalah mungkin untuk sepenuhnya menghindari overhead pada lingkungan produksi.)

Apa yang menjadi intinya adalah argumen hard error abadi vs soft error : IDisposable.Dispose( bool disposing )idiom adalah pendekatan kesalahan lunak, dan itu merupakan upaya untuk memungkinkan programmer lupa untuk memanggil Dispose()tanpa sistem gagal, jika mungkin. Pendekatan hard error mengatakan bahwa programmer harus selalu memastikan yang Dispose()akan dipanggil. Hukuman yang biasanya ditentukan oleh pendekatan kesalahan keras dalam banyak kasus adalah kegagalan pernyataan, tetapi untuk kasus khusus ini kami membuat pengecualian dan mengurangi hukuman menjadi penerbitan sederhana entri log kesalahan.

Jadi, agar mekanisme ini berfungsi, versi DEBUG dari aplikasi kita harus melakukan pembuangan sampah penuh sebelum berhenti, sehingga untuk menjamin bahwa semua destruktor akan dipanggil, dan dengan demikian menangkap IDisposablebenda apa pun yang kita lupa buang.


Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose()Sebenarnya tidak, meskipun saya tidak berpikir C # mampu melakukannya. Jangan memaparkan sumber daya; alih-alih berikan DSL untuk menggambarkan semua yang akan Anda lakukan dengannya (pada dasarnya, sebuah monad), ditambah fungsi yang memperoleh sumber daya, melakukan hal-hal, membebaskannya, dan mengembalikan hasilnya. Caranya adalah dengan menggunakan sistem tipe untuk menjamin bahwa jika seseorang menyelundupkan referensi ke sumber daya, itu tidak dapat digunakan dalam panggilan lain ke fungsi run.
Doval

2
Masalah dengan Dispose(bool disposing)(yang tidak didefinisikan IDisposableadalah bahwa ia digunakan untuk menangani pembersihan kedua objek yang dikelola dan tidak dikelola objek memiliki sebagai bidang (atau bertanggung jawab untuk), yang memecahkan masalah yang salah. Jika Anda membungkus semua objek yang tidak dikelola dalam objek yang dikelola tanpa objek sekali pakai lainnya yang perlu dikhawatirkan maka semua Dispose()metode akan menjadi salah satu dari mereka (meminta finalis melakukan pembersihan yang sama jika perlu) atau hanya memiliki objek yang dikelola untuk dibuang (tidak memiliki finalis sama sekali), dan kebutuhan akan bool disposingmenghilang
Jon Hanna

-1 saran buruk karena bagaimana finalisasi sebenarnya bekerja. Saya sangat setuju dengan pendapat Anda tentang dispose(disposing)idiom menjadi terribad, tapi saya katakan demikian karena orang-orang begitu sering menggunakan teknik itu dan finalizer ketika mereka hanya mengelola sumber daya ( DbConnectionobjek misalnya dikelola , tidak di-pinvoked atau com marshalled), dan ANDA HARUS HANYA PERNAH MELAKSANAKAN FINALIZER DENGAN KODE TIDAK DIKELOLA, PINVOKED, COM MARSHALLED, ATAU TIDAK AMAN . Saya merinci di atas dalam jawaban saya betapa finalizer yang sangat mahal, jangan menggunakannya kecuali Anda memiliki sumber daya yang tidak terkelola di kelas Anda.
Jimmy Hoffa

2
Saya hampir ingin memberi Anda +1 meskipun hanya karena Anda mengutuk sesuatu yang oleh banyak orang dianggap sebagai inti penting dalam dispose(dispoing)idiom tersebut, tetapi kenyataannya adalah itu hanya lazim karena orang-orang begitu takut pada barang-barang GC yang sesuatu yang tidak ada hubungannya dengan bahwa ( disposeharus ada kaitannya dengan GC) pantas mereka untuk hanya mengambil obat yang diresepkan tanpa bahkan menyelidikinya. Baik bagi Anda untuk memeriksanya, tetapi Anda melewatkan keseluruhan terbesar (itu mendorong finalizer jauh lebih sering daripada yang seharusnya)
Jimmy Hoffa

1
@JimmyHoffa terima kasih atas masukan Anda. Saya setuju bahwa finalizer biasanya hanya digunakan untuk melepaskan sumber daya yang tidak dikelola, tetapi tidakkah Anda setuju bahwa pada build DEBUG aturan ini tidak dapat diterapkan, dan pada build DEBUG kita harus bebas menggunakan finalizer untuk menangkap bug? Itu saja yang saya sarankan di sini, jadi saya tidak mengerti mengapa Anda mempermasalahkannya. Lihat juga programmers.stackexchange.com/questions/288715/... untuk penjelasan lebih panjang tentang pendekatan ini di sisi java dunia.
Mike Nakis

0

Bisakah Anda memberi tahu saya dalam skenario seperti apa sebenarnya ide yang baik atau masuk akal untuk memaksa pengumpulan sampah? Saya tidak meminta kasus khusus C # melainkan semua bahasa pemrograman yang memiliki pemulung. Saya tahu Anda tidak bisa memaksakan GC pada semua bahasa, seperti Java, tapi anggaplah Anda bisa.

Hanya berbicara dengan sangat teoretis dan mengabaikan isu-isu seperti beberapa implementasi GC memperlambat segalanya selama siklus pengumpulan mereka, skenario terbesar yang dapat saya pikirkan untuk memaksa pengumpulan sampah adalah perangkat lunak mission-critical di mana kebocoran logis lebih disukai daripada menggantung crash pointer, misalnya, karena menabrak pada waktu yang tak terduga mungkin menelan korban jiwa manusia atau semacamnya.

Jika Anda melihat beberapa game indie shoddier yang ditulis menggunakan bahasa GC seperti game Flash, mereka bocor seperti orang gila tetapi tidak rusak. Mereka mungkin membutuhkan sepuluh kali memori 20 menit untuk bermain game karena beberapa bagian dari basis kode permainan lupa untuk menetapkan referensi ke nol atau menghapusnya dari daftar, dan frame rate mungkin mulai menderita, tetapi permainan masih berfungsi. Gim serupa yang ditulis dengan menggunakan kode C atau C ++ yang jelek mungkin macet sebagai akibat dari mengakses pointer menggantung sebagai hasil dari jenis kesalahan manajemen sumber daya yang sama, tetapi itu tidak akan bocor begitu banyak.

Untuk game, crash mungkin lebih disukai dalam arti bahwa itu dapat dengan cepat dideteksi dan diperbaiki, tetapi untuk program mission-critical, crash pada waktu yang sama sekali tidak terduga dapat membunuh seseorang. Jadi kasus utama yang saya pikir akan menjadi skenario di mana tidak menabrak atau beberapa bentuk lainnya adalah keamanan sangat penting, dan kebocoran logis adalah hal yang relatif sepele dibandingkan.

Skenario utama di mana saya pikir itu buruk untuk memaksa GC adalah untuk hal-hal di mana kebocoran logis sebenarnya kurang disukai daripada kecelakaan. Dengan game, misalnya, crash tidak akan membunuh siapa pun dan mungkin mudah ditangkap dan diperbaiki selama pengujian internal, sedangkan kebocoran logis mungkin tidak diketahui bahkan setelah produk dikirimkan kecuali jika begitu parah sehingga membuat game tidak dapat dimainkan dalam hitungan menit . Di beberapa domain, crash yang mudah direproduksi yang terjadi dalam pengujian kadang-kadang lebih baik daripada kebocoran yang tidak diketahui orang dengan segera.

Kasus lain yang bisa saya pikirkan di mana mungkin lebih baik untuk memaksakan GC pada tim adalah untuk program yang sangat singkat, seperti hanya sesuatu yang dieksekusi dari baris perintah yang melakukan satu tugas dan kemudian dimatikan. Dalam hal ini masa hidup program terlalu singkat untuk membuat segala jenis kebocoran logis menjadi tidak sepele. Kebocoran logis, bahkan untuk sumber daya besar, biasanya hanya menjadi jam atau menit bermasalah setelah menjalankan perangkat lunak, sehingga perangkat lunak yang hanya dimaksudkan untuk dieksekusi selama 3 detik tidak akan pernah memiliki masalah dengan kebocoran logis, dan itu bisa membuat banyak hal lebih mudah untuk menulis program yang berumur pendek jika tim hanya menggunakan GC.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.