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.SuppressFinalize
yang 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 FReachable
antrian, 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.SuppressFinalize
akan dikumpulkan dalam Gen 2.