Dalam artikel ini Anda akan menemukan dua sumber informasi sekaligus:
- Kursus Lengkap Pengumpul Sampah dalam Bahasa Rusia: CLRium # 6 ( workshop saat ini di sini )
- Terjemahan sebuah artikel dari BOTR "Garbage Collector Device" oleh Maoni Stevens.

1. CLRium # 5: Kursus Kolektor Sampah Lengkap

2. Perangkat pengumpul sampah oleh Maoni Stephens ( @ maoni0 )
Catatan: untuk mempelajari lebih lanjut tentang pengumpulan sampah secara umum, lihat Buku Pegangan Pengumpulan Sampah ; informasi khusus tentang pemulung di CLR disediakan dalam buku Pro .NET Memory Management . Tautan ke kedua sumber diberikan di akhir dokumen.
Arsitektur Komponen
Pengumpulan sampah dikaitkan dengan dua komponen: dispenser dan kolektor. Pengalokasi bertanggung jawab untuk mengalokasikan memori dan memanggil kolektor jika perlu. Kolektor mengumpulkan sampah atau memori objek yang tidak lagi digunakan oleh program.
Ada cara lain untuk memanggil kolektor, misalnya secara manual, menggunakan GC.Collect. Juga, utas finalizer dapat menerima pemberitahuan asinkron bahwa kehabisan memori (yang akan menyebabkan kolektor).
Perangkat distributor
Distributor dipanggil oleh komponen tambahan runtime dengan informasi berikut:
- ukuran yang diperlukan dari plot yang dialokasikan;
- konteks alokasi memori untuk utas eksekusi;
- bendera yang menunjukkan, misalnya, apakah objek dapat diselesaikan.
Pengumpul sampah tidak menyediakan metode pemrosesan khusus untuk berbagai jenis objek. Ini menerima informasi tentang ukuran objek dari runtime.
Bergantung pada ukuran, kolektor membagi objek menjadi dua kategori: kecil (<85.000 byte) dan besar (> = 85.000 byte). Secara umum, perakitan benda kecil dan besar dapat terjadi dengan cara yang sama. Namun, kolektor memisahkannya berdasarkan ukuran, karena mengompresi objek besar membutuhkan banyak sumber daya.
Pengumpul sampah mengalokasikan memori ke pengalokasi berdasarkan konteks alokasi. Ukuran konteks alokasi ditentukan oleh blok memori yang dialokasikan.
Konteks seleksi adalah area kecil dari segmen heap tertentu, yang masing-masing ditujukan untuk aliran eksekusi tertentu. Pada mesin dengan satu prosesor (artinya 1 prosesor logis), konteks alokasi memori tunggal digunakan untuk objek generasi 0.
Blok memori yang dialokasikan - jumlah memori yang dialokasikan oleh pengalokasi setiap kali membutuhkan lebih banyak memori untuk memposisikan objek di dalam area. Ukuran blok biasanya 8 KB, dan ukuran rata-rata objek yang dikelola adalah 35 byte. Karena itu, dalam satu blok Anda dapat menempatkan banyak objek.
Objek besar tidak menggunakan konteks dan blok. Satu objek besar mungkin lebih besar dari potongan kecil memori ini. Selain itu, manfaat menggunakan area ini (dijelaskan di bawah) hanya terlihat ketika bekerja dengan benda kecil. Ruang untuk objek besar dialokasikan langsung di segmen heap.
Distributor dirancang sedemikian rupa sehingga:
panggil pengumpul sampah bila perlu: pengalokasi memanggil pengumpul ketika jumlah memori yang dialokasikan untuk objek melebihi nilai ambang batas (ditetapkan oleh pengumpul), atau jika pengalokasi tidak lagi dapat mengalokasikan memori di segmen ini. Ambang dan segmen yang dikontrol akan dijelaskan secara rinci nanti.
menyimpan lokasi objek: objek yang terletak bersama dalam satu segmen tumpukan disimpan di alamat virtual yang berdekatan satu sama lain.
gunakan cache secara efisien: pengalokasi mengalokasikan memori dalam blok , dan bukan untuk setiap objek. Ini mengeluarkan begitu banyak memori untuk menyiapkan cache prosesor, karena beberapa objek akan ditempatkan langsung di dalamnya. Blok memori yang dialokasikan biasanya 8 KB.
secara efektif membatasi area yang dialokasikan untuk utas eksekusi: kedekatan konteks dan blok memori yang dialokasikan untuk utas memastikan bahwa hanya satu utas yang akan menulis data ke ruang yang dialokasikan. Akibatnya, tidak perlu membatasi alokasi memori sampai ruang dalam konteks alokasi saat ini telah berakhir.
memastikan integritas memori: pengumpul sampah selalu mengatur ulang memori untuk objek yang baru dialokasikan sehingga tautannya tidak mengarah ke bagian memori yang berubah-ubah.
memastikan heap continuity: pengalokasi menciptakan objek bebas dari memori yang tersisa di setiap blok yang dialokasikan. Misalnya, jika 30 byte dibiarkan di blok, dan 40 byte diperlukan untuk mengakomodasi objek berikutnya, pengalokasi akan mengubah 30 byte ini menjadi objek bebas dan meminta blok memori baru.
API
Object* GCHeap::Alloc(size_t size, DWORD); Object* GCHeap::Alloc(alloc_context* acontext, size_t size, DWORD);
Dengan menggunakan fungsi-fungsi ini, Anda dapat mengalokasikan memori untuk objek kecil dan besar. Ada fungsi untuk mengalokasikan ruang tepat di tumpukan benda besar (LOH):
Object* GCHeap::AllocLHeap(size_t size, DWORD);
Perangkat kolektor
Tugas Pengumpul Sampah
GC dirancang untuk manajemen memori yang efisien. Pengembang yang menulis kode terkelola dapat menggunakannya tanpa banyak usaha. Tata pemerintahan yang baik berarti:
- pengumpulan sampah harus cukup sering terjadi agar tidak mengotori tumpukan yang dikelola dengan sejumlah besar (berdasarkan rasio atau dalam jumlah absolut) dari objek yang tidak digunakan (sampah) yang memori dialokasikan;
- pengumpulan sampah harus dilakukan sesering mungkin agar tidak membuang waktu prosesor yang berguna, meskipun pengumpulan yang lebih sering akan memungkinkan penggunaan memori yang lebih sedikit;
- pengumpulan sampah harus produktif, karena jika sebagai hasil dari perakitan hanya sepotong kecil memori yang dibebaskan, maka baik perakitan dan waktu prosesor yang dihabiskan sia-sia;
- pengumpulan sampah harus cepat, karena banyak beban kerja memerlukan waktu tunda yang singkat;
- pengembang yang menulis kode terkelola tidak perlu tahu banyak tentang pengumpulan sampah untuk mencapai penggunaan memori yang efisien (dibandingkan dengan beban kerja mereka);
- Pengumpul sampah harus beradaptasi dengan sifat penggunaan memori yang berbeda.
Deskripsi logis dari Heap yang Dikelola
Pengumpul sampah CLR mengumpulkan objek yang secara logis dipisahkan oleh generasi. Setelah merakit objek dalam generasi N , objek yang tersisa ditandai sebagai milik generasi N + 1 . Proses ini disebut promosi benda lintas generasi. Ada pengecualian dalam proses ini ketika perlu untuk mentransfer objek ke generasi yang lebih rendah atau tidak memajukannya sama sekali.
Dalam kasus benda kecil, tumpukan dibagi menjadi tiga generasi: gen0, gen1 dan gen2. Untuk objek besar, hanya ada satu generasi - gen3. Gen0 dan gen1 disebut generasi fana (objek hidup dalam waktu singkat).
Untuk sekelompok benda kecil, jumlah generasi berarti usia mereka. Misalnya, gen0 adalah generasi termuda. Ini tidak berarti bahwa semua objek di gen0 lebih muda dari objek di gen1 atau gen2. Ada beberapa pengecualian yang dijelaskan di bawah ini. Merakit satu generasi berarti mengumpulkan objek dalam generasi ini, serta semua generasi mudanya.
Secara teoritis, perakitan benda besar dan kecil dapat terjadi dengan cara yang sama. Namun, karena kompresi objek besar membutuhkan banyak sumber daya, perakitannya berlangsung dengan cara yang berbeda. Benda-benda besar hanya terkandung dalam gen2 dan hanya dikumpulkan selama pengumpulan sampah di generasi ini karena alasan kinerja. Baik gen2 dan gen3 bisa berukuran besar, dan membangun objek dalam generasi sementara (gen0 dan gen1) tidak boleh terlalu intensif sumber daya.
Objek ditempatkan pada generasi termuda. Untuk objek kecil, ini adalah gen0, dan untuk objek besar, gen3.
Deskripsi Fisik Tumpukan Terkelola
Tumpukan terkelola terdiri dari sekumpulan segmen. Segmen adalah blok memori yang terus-menerus yang dilewati OS ke pengumpul sampah. Segmen heap dibagi menjadi beberapa bagian kecil dan besar untuk mengakomodasi objek kecil dan besar. Segmen setiap tumpukan dihubungkan bersama. Setidaknya satu segmen untuk objek kecil dan satu untuk segmen besar dicadangkan saat memuat CLR.
Di setiap tumpukan benda kecil hanya ada satu segmen fana, di mana generasi gen0 dan gen1 berada. Segmen ini mungkin atau mungkin tidak mengandung objek generasi gen2. Selain segmen fana, satu atau lebih segmen tambahan mungkin ada, yang akan menjadi segmen gen2, karena mengandung segmen generasi 2.
Tumpukan benda besar terdiri dari satu atau lebih segmen.
Segmen tumpukan diisi dari alamat yang lebih rendah ke yang lebih tinggi. Ini berarti bahwa objek yang terletak di alamat bawah segmen lebih tua dari yang terletak di senior. Ada juga pengecualian yang dijelaskan di bawah ini.
Segmen heap dialokasikan sesuai kebutuhan. Jika mereka tidak mengandung objek yang digunakan, segmen dihapus. Namun, segmen awal pada heap selalu ada. Satu segmen dialokasikan pada satu waktu untuk setiap tumpukan. Dalam hal benda kecil, ini terjadi selama pengumpulan sampah, dan untuk benda besar, selama alokasi memori untuk mereka. Skema semacam itu meningkatkan produktivitas, karena benda-benda besar dikumpulkan hanya pada generasi 2 (yang membutuhkan banyak sumber daya).
Segmen tumpukan bergabung bersama dalam pemilihan. Segmen terakhir dalam rantai selalu singkat. Segmen tempat semua objek dikumpulkan dapat digunakan kembali, misalnya, sebagai sementara. Penggunaan kembali segmen hanya berlaku untuk tumpukan benda kecil. Untuk mengakomodasi objek besar setiap kali seluruh objek besar dipertimbangkan. Benda kecil ditempatkan hanya di segmen fana.
Nilai ambang memori yang dialokasikan
Ini adalah konsep logis terkait dengan ukuran setiap generasi. Jika terlampaui, generasi memulai pengumpulan sampah.
Nilai ambang batas untuk generasi tertentu diatur tergantung pada jumlah objek yang bertahan dalam generasi ini. Jika jumlah ini tinggi, nilai ambang batas menjadi lebih tinggi. Diharapkan bahwa rasio objek yang digunakan dan yang tidak digunakan akan lebih baik selama sesi pengumpulan sampah generasi berikutnya.
Seleksi generasi untuk pengumpulan sampah
Saat diaktifkan, kolektor harus menentukan generasi yang akan dibangun. Selain nilai ambang, faktor lain memengaruhi pilihan ini:
- fragmentasi satu generasi - jika satu generasi sangat terfragmentasi, pengumpulan sampah di dalamnya cenderung produktif;
- jika memori mesin terlalu sibuk, kolektor dapat melakukan pembersihan yang lebih dalam, jika pembersihan seperti itu lebih cenderung membebaskan ruang dan menghindari pertukaran halaman yang tidak perlu (memori di seluruh mesin);
- jika segmen fana kehabisan ruang, kolektor dapat melakukan pembersihan yang lebih dalam di segmen ini (mengumpulkan lebih banyak objek generasi 1) untuk menghindari mengalokasikan segmen tumpukan baru.
Proses pengumpulan sampah
Tahap penandaan
Selama fase ini, CLR harus menemukan semua benda hidup.
Keuntungan dari seorang kolektor dengan dukungan generasi adalah kemampuannya untuk membersihkan sampah hanya di bagian tumpukan, daripada terus-menerus mengamati semua benda. Mengumpulkan sampah dalam generasi sementara, pengumpul harus menerima informasi dari lingkungan runtime tentang objek mana dalam generasi ini yang masih digunakan oleh program. Selain itu, objek pada generasi yang lebih tua dapat menggunakan objek pada generasi yang lebih muda, mengacu pada mereka.
Untuk menandai objek lama yang mereferensikan objek baru, pemulung menggunakan bit khusus. Bit diatur oleh mekanisme kompiler JIT selama operasi penugasan. Jika objek milik generasi ephemeral, kompiler JIT akan mengatur byte yang mengandung bit yang mengindikasikan posisi awal. Mengumpulkan sampah dalam generasi sementara, kolektor dapat menggunakan bit ini untuk seluruh tumpukan yang tersisa dan hanya melihat objek yang sesuai dengan bit ini.
Tahap perencanaan
Pada titik ini, kompresi dimodelkan untuk menentukan efektivitasnya. Jika hasilnya produktif, pengumpul memulai kompresi yang sebenarnya. Kalau tidak, dia hanya membersihkan.
Pindah panggung
Jika kolektor melakukan kompresi, ini akan menyebabkan benda bergerak. Dalam hal ini, Anda harus memperbarui tautan ke objek-objek ini. Selama fase pemindahan, pengumpul harus menemukan semua tautan yang menunjuk ke objek dalam generasi tempat pengumpulan sampah berlangsung. Sebaliknya, selama tahap penandaan, kolektor hanya menandai objek hidup, sehingga tidak perlu mempertimbangkan tautan lemah.
Tahap kompresi
Tahap ini cukup sederhana, karena kolektor telah menentukan alamat baru untuk objek bergerak selama tahap perencanaan. Ketika dikompresi, objek akan disalin ke alamat ini.
Tahap pembersihan
Selama fase ini, kolektor mencari ruang yang tidak digunakan antara benda hidup. Alih-alih ruang ini, ia menciptakan objek gratis. Objek yang tidak digunakan di sekitarnya menjadi satu objek gratis. Semua objek gratis ditempatkan dalam daftar objek gratis .
Aliran kode
Ketentuan:
- WKS GC: Pengumpulan Sampah dalam Mode Workstation
- SVR GC: Pengumpulan Sampah Mode Server
Perilaku fungsional
WKS GC tanpa pengumpulan sampah paralel
- Utas pengguna menggunakan semua memori yang dialokasikan untuknya dan memanggil pengumpul sampah.
- Kolektor memanggil
SuspendEE
untuk menangguhkan semua utas yang dikelola. - Kolektor memilih generasi untuk dibersihkan.
- Penandaan objek dimulai.
- Kolektor pergi ke tahap perencanaan dan menentukan kebutuhan untuk kompresi.
- Jika perlu, kolektor memindahkan objek dan melakukan kompresi. Dalam kasus lain, itu hanya pembersihan.
- Kolektor memanggil
RestartEE
untuk memulai kembali utas yang dikelola. - Utas pengguna terus bekerja.
WKS GC dengan pengumpulan sampah paralel
Algoritma ini menjelaskan pengumpulan sampah latar belakang.
- Utas pengguna menggunakan semua memori yang dialokasikan untuknya dan memanggil pengumpul sampah.
- Kolektor memanggil
SuspendEE
untuk menangguhkan semua utas yang dikelola. - Kolektor menentukan apakah akan menjalankan pengumpulan sampah latar belakang.
- Jika demikian, utas pengumpulan sampah latar belakang diaktifkan. Utas ini memanggil
RestartEE
untuk melanjutkan utas yang dikelola. - Alokasi memori untuk proses yang dikelola berlanjut pada saat yang sama dengan pengumpulan sampah latar belakang.
- Utas pengguna dapat menggunakan semua memori yang dialokasikan untuk itu dan memulai pengumpulan sampah sementara (juga dikenal sebagai pengumpulan sampah prioritas tinggi). Ini berjalan dengan cara yang sama seperti dalam mode workstation tanpa pengumpulan sampah paralel.
SuspendEE
pengumpulan sampah latar belakang memanggil SuspendEE
lagi untuk menyelesaikan tanda, dan kemudian memanggil RestartEE
untuk memulai pembersihan paralel dengan utas pengguna berjalan.- Pengumpulan sampah latar belakang selesai.
SVR GC tanpa pengumpulan sampah paralel
- Utas pengguna menggunakan semua memori yang dialokasikan untuknya dan memanggil pengumpul sampah.
- Utas pengumpulan sampah mode server diaktifkan dan menyebabkan
SuspendEE
menghentikan sementara eksekusi utas yang dikelola. - Aliran pengumpulan sampah dalam mode server melakukan operasi yang sama seperti dalam mode workstation tanpa pengumpulan sampah paralel.
- Utas pengumpulan sampah mode server meminta
RestartEE
untuk memulai utas yang dikelola. - Utas pengguna terus bekerja.
SVR GC dengan pengumpulan sampah paralel
Algoritma ini sama seperti dalam kasus pengumpulan sampah paralel dalam mode workstation, hanya pengumpulan non-fonon dilakukan di utas server.
Arsitektur fisik
Bagian ini akan membantu Anda memahami aliran kode.
Ketika utas pengguna kehabisan memori, itu bisa mendapatkan ruang kosong menggunakan fungsi try_allocate_more_space
.
Fungsi try_allocate_more_space
memanggil GarbageCollectGeneration
ketika Anda perlu memulai pengumpul sampah.
Jika pengumpulan sampah dalam mode workstation tidak paralel, GarbageCollectGeneration
dijalankan di utas pengguna yang disebut oleh pemulung. Aliran kode adalah sebagai berikut:
GarbageCollectGeneration() { SuspendEE(); garbage_collect(); RestartEE(); } garbage_collect() { generation_to_condemn(); gc1(); } gc1() { mark_phase(); plan_phase(); } plan_phase() { // , // if (compact) { relocate_phase(); compact_phase(); } else make_free_lists(); }
Jika pengumpulan sampah paralel dilakukan dalam mode workstation (secara default), aliran kode untuk pengumpulan sampah latar belakang terlihat seperti ini:
GarbageCollectGeneration() { SuspendEE(); garbage_collect(); RestartEE(); } garbage_collect() { generation_to_condemn(); // // do_background_gc(); } do_background_gc() { init_background_gc(); start_c_gc (); // . wait_to_proceed(); } bgc_thread_function() { while (1) { // // gc1(); } } gc1() { background_mark_phase(); background_sweep(); }
Tautan Sumber Daya