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.
Jawaban:
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 yangMain()
referensi karena mereka akan di-root sampai AndaMain()
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 dengan
GC.SuppressFinalize
yang umum dalamDispose()
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 padaFReachable
antrian, tetapi koleksi tidak melakukan hal lain dengannya, sehingga ia hidup ke dalam Gen 2. Dengan cara ini, semua objek yang memiliki finalizer, dan tidakGC.SuppressFinalize
akan dikumpulkan dalam Gen 2.sumber
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).
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.
sumber
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.)
sumber
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
struct
cara, jauh lebih sering daripada biasanya, untuk meminimalkan jumlah objek yang perlu dikumpulkan sampah.sumber
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
close
metode 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.
sumber
using
blok atau memanggilClose
metode 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).close
metode tersedia (atau sumber daya dapat digunakan denganusing
blok), ini adalah pendekatan yang tepat. Jawabannya secara khusus berkaitan dengan kasus-kasus langka di mana mekanisme ini tidak tersedia.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.
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.
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 "
sumber
Ada beberapa kasus di mana Anda mungkin ingin memanggil gc () sendiri.
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 menelepongc()
secara manual, tidak akan ada grafik rujukan cek berlebihan pada bagian dari koleksi besar objek yang Anda muat ke dalam memori. Singkatnya, jika Anda menjalankangc()
sebelum Anda memuat banyak ke dalam memori,gc()
diinduksi selama beban terjadi kurang dari setidaknya satu kali ketika memuat mulai membuat tekanan memori.besarobjek dan Anda tidak akan memuat lebih banyak objek ke dalam memori. Singkatnya, Anda beralih dari membuat fase ke menggunakan fase. Dengan memanggilgc()
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 .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 kinerjasumber
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.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 memanggilGC.Collect()
sisa waktu aplikasi berjalan.Kasus dunia nyata ini adalah contoh yang baik dari pedoman kapan kita harus menggunakan
GC.Collect()
: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).sumber
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 ketikaIDisposable.Dispose()
dipanggil dari dalam destruktor, karena mereka telah dirawat oleh pengumpul sampah pada saat itu, jadi ada Apakah ini kebutuhan untukDispose()
metode terpisah yang menerimabool disposing
bendera 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 bahwadisposed
bendera objek sekali pakai tidak pernah disetel ketrue
. 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 DEBUG
blok, 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 memanggilDispose()
tanpa sistem gagal, jika mungkin. Pendekatan hard error mengatakan bahwa programmer harus selalu memastikan yangDispose()
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
IDisposable
benda apa pun yang kita lupa buang.sumber
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.Dispose(bool disposing)
(yang tidak didefinisikanIDisposable
adalah 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 semuaDispose()
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 akanbool disposing
menghilangdispose(disposing)
idiom menjadi terribad, tapi saya katakan demikian karena orang-orang begitu sering menggunakan teknik itu dan finalizer ketika mereka hanya mengelola sumber daya (DbConnection
objek 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.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 (dispose
harus 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)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.
sumber