Dasar-dasar pengumpulan sampah

Dalam runtime bahasa umum (CLR), pengumpul sampah (GC) berfungsi sebagai sebuah manajer memori otomatis. Pengumpul sampah mengelola alokasi dan rilis memori untuk aplikasi Anda. Oleh karena itu, pengembang yang bekerja dengan kode terkelola tidak perlu menulis kode untuk melakukan tugas manajemen memori. Manajemen memori otomatis dapat menghilangkan masalah umum seperti lupa membebaskan objek dan menyebabkan kebocoran memori atau mencoba mengakses memori yang dibebaskan untuk objek yang sudah dibebaskan.

Artikel ini menjelaskan konsep inti pengumpulan sampah.

Keuntungan

Pengumpul sampah memberikan keuntungan berikut:

  • Membebaskan pengembang dari kewajiban untuk merilis memori secara manual.

  • Mengalokasikan objek pada timbunan terkelola secara efisien.

  • Mengeklaim kembali objek yang tidak lagi digunakan, menghapus memorinya, dan menjaga memorinya tetap tersedia untuk alokasi di masa mendatang. Objek terkelola secara otomatis mendapatkan konten bersih untuk memulai, sehingga konstruktornya tidak perlu menginisialisasi setiap bidang data.

  • Memberikan keamanan memori dengan memastikan bahwa objek tidak dapat menggunakan untuk dirinya sendiri memori yang dialokasikan untuk objek lain.

Dasar-dasar memori

Daftar berikut ini meringkas konsep memori CLR penting:

  • Setiap proses memiliki ruang alamat virtual sendiri yang terpisah. Semua proses pada komputer yang sama berbagi memori fisik dan file halaman yang sama, jika ada.

  • Secara default, pada komputer 32-bit, setiap proses memiliki ruang alamat virtual mode pengguna 2 GB.

  • Sebagai pengembang aplikasi, Anda hanya akan bekerja dengan ruang alamat virtual dan tidak memanipulasi memori fisik secara langsung. Pengumpul sampah mengalokasikan dan membebaskan memori virtual untuk Anda pada timbunan yang terkelola.

    Jika Anda menulis kode asli, Anda menggunakan fungsi Windows untuk bekerja dengan ruang alamat virtual. Fungsi-fungsi ini mengalokasikan dan membebaskan memori virtual untuk Anda pada timbunan asli.

  • Memori virtual memiliki tiga status:

    Provinsi Deskripsi
    Gratis Blok memori tidak memiliki referensi ke dalamnya dan tersedia untuk alokasi.
    Dicadangkan Blok memori tersedia untuk Anda gunakan dan tidak dapat digunakan untuk permintaan alokasi lainnya. Namun, Anda tidak dapat menyimpan data ke blok memori ini sampai dilakukan.
    Diterapkan Blok memori ditetapkan ke penyimpanan fisik.
  • Ruang alamat virtual bisa terfragmentasi, yang berarti bahwa ada blok gratis yang dikenal sebagai lubang di ruang alamat. Ketika alokasi memori virtual diminta, manajer memori virtual harus menemukan satu blok gratis yang cukup besar untuk memenuhi permintaan alokasi. Bahkan jika Anda memiliki ruang kosong 2 GB, alokasi yang membutuhkan 2 GB tidak akan berhasil kecuali jika semua ruang kosong itu berada dalam satu blok alamat.

  • Anda dapat kehabisan memori jika tidak ada cukup ruang alamat virtual untuk dicadangkan, atau ruang fisik untuk diterapkan.

    File halaman digunakan bahkan jika tekanan memori fisik (permintaan memori fisik) rendah. Pertama kali tekanan memori fisik tinggi, sistem operasi harus memberi ruang dalam memori fisik untuk menyimpan data, dan mencadangkan beberapa data yang berada dalam memori fisik ke file halaman. Data tidak di-halaman sampai diperlukan, jadi dimungkinkan untuk menemukan penomor dalam situasi di mana tekanan memori fisik rendah.

Alokasi Memori

Saat Anda menginisialisasi proses baru, runtime menyimpan wilayah ruang alamat yang berdekatan untuk proses tersebut. Ruang alamat yang dicadangkan ini disebut timbunan terkelola. Timbunan terkelola mempertahankan penunjuk ke alamat tempat objek berikutnya yang ada di timbunan akan dialokasikan. Awalnya, penunjuk ini diatur ke alamat dasar timbunan terkelola. Semua jenis referensi dialokasikan pada timbunan terkelola. Ketika aplikasi membuat jenis referensi pertama, memori dialokasikan untuk jenis di alamat dasar timbunan terkelola. Ketika aplikasi membuat objek berikutnya, runtime mengalokasikan memori untuk itu di ruang alamat segera mengikuti objek pertama. Selama ruang alamat tersedia, runtime terus mengalokasikan ruang untuk objek baru dengan cara ini.

Mengalokasikan memori dari tumpukan terkelola lebih cepat daripada alokasi memori yang tidak terkelola. Karena runtime mengalokasikan memori untuk suatu objek dengan menambahkan nilai ke penunjuk, prosesnya hampir secepat mengalokasikan memori dari tumpukan. Selain itu, karena objek baru yang dialokasikan secara berurutan disimpan secara berdekatan di timbunan terkelola, aplikasi dapat mengakses objek dengan cepat.

Rilis memori

Mesin pengoptimal pengumpul sampah menentukan waktu terbaik untuk menjalankan pengumpulan berdasarkan alokasi yang dibuat. Ketika pengumpul sampah melakukan pengumpulan, ia melepaskan memori untuk objek yang tidak lagi digunakan oleh aplikasi. Pengumpul sampah menentukan objek mana yang tidak lagi digunakan dengan memeriksa akar aplikasi. Akar aplikasi mencakup bidang statik, variabel lokal pada tumpukan utas, register CPU, handel GC, dan antrean finalisasi. Setiap akar merujuk pada objek di timbunan terkelola atau diatur ke null. Pengumpul sampah dapat meminta sisa runtime untuk akar ini. Pengumpul sampah menggunakan daftar ini untuk membuat grafik yang berisi semua objek yang dapat dijangkau dari akar.

Objek yang tidak ada dalam grafik tidak dapat dijangkau dari akar aplikasi. Pengumpul sampah menganggap objek yang tidak terjangkau sebagai sampah dan akan melepaskan memori yang dialokasikan objek tersebut. Selama pengumpulan, pengumpul sampah memeriksa tumpukan yang dikelola, mencari blok ruang alamat yang ditempati oleh benda-benda yang tidak terjangkau. Setelah menemukan setiap objek yang tidak dapat dijangkau, pengumpul sampah akan menggunakan fungsi penyalinan memori untuk memadatkan objek yang dapat dijangkau dalam memori, sehingga akan membebaskan blok ruang alamat yang dialokasikan ke objek yang tidak dapat dijangkau. Setelah memori untuk objek yang dapat dijangkau telah dipadatkan, pengumpul sampah membuat koreksi penunjuk yang diperlukan sehingga akar aplikasi menunjuk ke objek di lokasi barunya. Ini juga memosisikan penunjuk tumpukan terkelola setelah objek terakhir yang dapat dijangkau.

Memori dipadatkan hanya jika pengumpulan menemukan objek yang tidak dapat dijangkau dalam jumlah besar. Jika semua objek dalam tumpukan terkelola bertahan dari koleksi, maka tidak perlu pemadatan memori.

Untuk meningkatkan performa, runtime mengalokasikan memori untuk objek besar dalam timbunan terpisah. Pengumpul sampah secara otomatis melepaskan memori untuk benda-benda besar. Namun, untuk menghindari memindahkan objek besar dalam memori, memori ini biasanya tidak dipadatkan.

Kondisi untuk pengumpulan sampah

Pengumpulan sampah terjadi ketika salah satu kondisi berikut terjadi:

  • Sistem memiliki memori fisik yang rendah. Ukuran memori terdeteksi oleh pemberitahuan memori rendah dari sistem operasi atau memori rendah seperti yang ditunjukkan oleh host.

  • Memori yang digunakan oleh objek yang dialokasikan pada timbunan terkelola melampaui ambang yang dapat diterima. Ambang batas ini akan secara terus menerus disesuaikan saat proses berjalan.

  • Metode GC.Collect dipanggil. Dalam hampir semua kasus, Anda tidak perlu memanggil metode ini karena pengumpul sampah berjalan terus menerus. Metode ini terutama digunakan untuk situasi dan pengujian unik.

Timbunan terkelola

Setelah CLR menginisialisasi pengumpul sampah, CLR mengalokasikan segmen memori untuk menyimpan dan mengelola objek. Memori ini disebut timbunan terkelola, berbeda dengan timbunan asli dalam sistem operasi.

Ada tumpukan terkelola untuk setiap proses terkelola. Semua utas dalam proses mengalokasikan memori untuk objek pada timbunan yang sama.

Untuk mencadangkan memori, pengumpul sampah memanggil fungsi Windows VirtualAlloc dan mencadangkan satu segmen memori pada satu waktu untuk aplikasi terkelola. Pengumpul sampah juga memesan segmen sesuai kebutuhan dan melepaskan segmen kembali ke sistem operasi (setelah membersihkannya dari objek apa pun) dengan memanggil fungsi Windows VirtualFree .

Penting

Ukuran segmen yang dialokasikan oleh pengumpul sampah adalah khusus untuk implementasi dan dapat berubah sewaktu-waktu, termasuk dalam pembaruan berkala. Aplikasi Anda tidak boleh membuat asumsi tentang atau bergantung pada ukuran segmen tertentu, juga tidak boleh mencoba mengonfigurasi jumlah memori yang tersedia untuk alokasi segmen.

Semakin sedikit objek yang dialokasikan pada timbunan, semakin sedikit pula pekerjaan yang harus dilakukan pengumpul sampah. Saat Anda mengalokasikan objek, jangan gunakan nilai yang dibulatkan, yang melebihi kebutuhan Anda, seperti mengalokasikan array 32 byte saat Anda hanya membutuhkan 15 byte.

Ketika pengumpulan sampah dipicu, pengumpul sampah akan mengeklaim kembali memori yang ditempati oleh objek mati. Proses reklamasi memampatkan objek langsung sehingga mereka dipindahkan bersama-sama, dan ruang mati dihapus, sehingga membuat tumpukan lebih kecil. Proses ini memastikan bahwa objek yang dialokasikan bersama tetap bersama di timbunan terkelola untuk mempertahankan lokalitas mereka.

Gangguan (frekuensi dan durasi) pengumpulan sampah adalah hasil dari volume alokasi dan jumlah memori yang bertahan pada timbunan terkelola.

Timbunan dapat dianggap sebagai akumulasi dua timbunan: timbunan objek besar dan timbunan objek kecil. Timbunan objek besar berisi objek yang besarnya 85.000 byte atau lebih, yang biasanya merupakan array. Sangat jarang objek instans menjadi ekstra besar.

Tip

Anda dapat mengonfigurasi ukuran ambang batas untuk objek yang termasuk timbunan objek besar.

Generasi

Algoritma GC didasarkan pada beberapa pertimbangan:

  • Memadatkan memori pada sebagian dari timbunan terkelola berlangsung lebih cepat dibandingkan seluruh timbunan terkelola.
  • Objek yang lebih baru memiliki masa pakai yang lebih pendek, dan objek yang lebih lama memiliki masa pakai yang lebih lama.
  • Objek yang lebih baru cenderung terkait satu sama lain dan diakses oleh aplikasi di waktu yang sama.

Pengumpulan sampah terutama terjadi bersama pengeklaiman kembali objek berumur pendek. Untuk mengoptimalkan performa pengumpul sampah, timbunan terkelola dibagi menjadi tiga generasi, 0, 1, dan 2, sehingga dapat menangani objek yang berumur panjang dan berumur pendek secara terpisah. Pengumpul sampah akan menyimpan objek baru di generasi 0. Objek yang dibuat di awal masa pakai aplikasi dan yang bertahan dari pengumpulan akan dipromosikan dan disimpan dalam generasi 1 dan 2. Karena memadatkan sebagian timbunan terkelola berlangsung lebih cepat daripada seluruhnya, skema ini memungkinkan pengumpul sampah untuk merilis memori dalam generasi tertentu alih-alih merilis memori untuk seluruh timbunan terkelola setiap kali melakukan pengumpulan.

  • Generasi 0: Generasi ini adalah yang termuda dan berisi objek berumur pendek. Contoh objek berumur pendek adalah variabel sementara. Pengumpulan sampah paling sering terjadi pada generasi ini.

    Objek yang baru dialokasikan membentuk objek generasi baru dan secara implisit merupakan koleksi generasi 0. Namun, jika mereka adalah objek besar, mereka pergi pada tumpangan objek besar (LOH), yang kadang-kadang disebut sebagai generasi 3. Generasi 3 adalah generasi fisik yang dikumpulkan secara logis sebagai bagian dari generasi 2.

    Sebagian besar objek diklaim ulang untuk pengumpulan sampah di generasi 0 dan tidak bertahan hingga generasi berikutnya.

    Jika aplikasi mencoba membuat objek baru ketika generasi 0 penuh, pengumpul sampah melakukan koleksi untuk membebaskan ruang alamat untuk objek. Pengumpul sampah memulai dengan memeriksa objek-objek di generasi 0 alih-alih semua objek yang ada di timbunan terkelola. Selain itu, memori yang diklaim kembali dalam pengumpulan generasi 0 saja sering kali sudah cukup untuk memungkinkan aplikasi melanjutkan pembuatan objek baru.

  • Generasi 1: Generasi ini berisi objek berumur pendek dan berfungsi sebagai buffer antara objek berumur pendek dan objek berumur panjang.

    Setelah pengumpul sampah melakukan pengumpulan generasi 0, memori untuk objek yang dapat dijangkau akan dipadatkan dan dipromosikan ke generasi 1. Karena objek yang bertahan dari pengumpulan cenderung memiliki masa hidup yang lebih lama, sangat wajar untuk mempromosikannya ke generasi yang lebih tinggi. Pengumpul sampah tidak perlu memeriksa kembali objek-objek di generasi 1 dan 2 setiap kali melakukan pengumpulan generasi 0.

    Jika kumpulan generasi 0 tidak mengklaim kembali memori yang cukup bagi aplikasi untuk membuat objek baru, pengumpul sampah dapat melakukan koleksi generasi 1 dan kemudian generasi 2. Objek di generasi 1 yang bertahan dari pengumpulan akan dipromosikan ke generasi 2.

  • Generasi 2: Generasi ini berisi objek berumur panjang. Contoh objek berumur panjang adalah objek dalam aplikasi server yang berisi data statik yang aktif selama proses.

    Objek di generasi 2 yang bertahan dari koleksi tetap berada di generasi 2 sampai mereka bertekad untuk tidak dapat dijangkau dalam koleksi di masa depan.

    Objek pada timbunan objek besar (yang kadang kala disebut sebagai generasi 3) juga dikumpulkan di generasi 2.

Pengumpulan sampah terjadi dalam generasi tertentu sebagai jaminan kondisi. Mengumpulkan generasi berarti mengumpulkan objek dalam generasi itu dan semua generasi mudanya. Pengumpulan sampah generasi 2 juga dikenal sebagai pengumpulan sampah penuh karena merebut kembali objek dalam semua generasi (yaitu, semua objek dalam tumpukan yang dikelola).

Bertahan dan promosi

Objek yang tidak direklamasi dalam pengumpulan sampah dikenal sebagai penyintas dan dipromosikan ke generasi berikutnya:

  • Objek yang bertahan dari pengumpulan sampah generasi 0 akan dipromosikan ke generasi 1.
  • Objek yang bertahan dari pengumpulan sampah generasi 1 akan dipromosikan ke generasi 2.
  • Objek yang bertahan dari pengumpulan sampah generasi 2 akan tetap berada di generasi 2.

Ketika pengumpul sampah mendeteksi bahwa adanya tingkat bertahan yang tinggi di suatu generasi, keadaan ini akan meningkatkan ambang batas alokasi untuk generasi tersebut. Pengumpulan berikutnya akan mendapatkan memori yang diklaim ulang dengan ukuran yang besar. CLR secara terus-menerus menyeimbangkan dua prioritas: tidak membiarkan set kerja aplikasi terlalu besar dengan menunda pengumpulan sampah dan tidak membiarkan pengumpulan sampah dilakukan terlalu sering.

Generasi dan segmen ephemeral

Karena objek pada generasi 0 dan 1 berumur pendek, generasi-generasi ini dikenal sebagai generasi ephemeral.

Generasi ephemeral dialokasikan di segmen memori yang dikenal sebagai segmen ephemeral. Setiap segmen baru yang diperoleh oleh pengumpul sampah akan menjadi segmen ephemeral baru dan berisi objek yang bertahan dari pengumpulan sampah generasi 0. Segmen ephemeral lama akan menjadi segmen generasi 2 yang baru.

Ukuran segmen sementara bervariasi tergantung pada apakah sistem 32-bit atau 64-bit dan pada jenis pengumpul sampah yang dijalankannya (stasiun kerja atau server GC). Tabel berikut ini memperlihatkan ukuran default segmen ephemeral:

Tempat kerja/server GC 32-bit 64-bit
Pengumpulan sampah stasiun kerja 16 MB 256 MB
Server pengumpulan sampah 64 MB 4 GB
Server GC dengan > 4 CPU logis 32 MB 2 GB
Server GC dengan > 8 CPU logis 16 MB 1 GB

Segmen ephemeral dapat mencakup objek generasi 2. Objek Generasi 2 dapat menggunakan beberapa segmen sebanyak yang diperlukan proses dan memori Anda.

Jumlah memori yang dibebaskan dari pengumpulan sampah ephemeral terbatas pada ukuran segmen ephemeral. Jumlah memori yang dibesarkan sebanding dengan ruang yang ditempati oleh objek mati.

Hal-hal yang terjadi selama pengumpulan sampah

Pengumpulan sampah memiliki fase-fase berikut:

  • Fase penandaan, yang akan menemukan dan membuat daftar semua objek langsung.

  • Fase relokasi, yang akan memperbarui referensi ke objek yang akan dipadatkan.

  • Fase pemadatan, yang akan mengeklaim kembali ruang yang ditempati oleh objek mati dan memadatkan objek yang bertahan. Fase pemadatan memindahkan objek yang telah bertahan dari pengumpulan sampah menuju ujung segmen yang lebih lama.

    Karena pengumpulan generasi 2 dapat menempati beberapa segmen, objek yang dipromosikan ke generasi 2 dapat dipindahkan ke segmen yang lebih lama. Penyintas generasi 1 dan generasi 2 dapat dipindahkan ke segmen yang berbeda karena dipromosikan ke generasi 2.

    Biasanya, tumpukan objek besar (LOH) tidak dikompilasi karena menyalin objek besar memberlakukan penalti performa. Namun, di .NET Core dan di .NET Framework 4.5.1 dan yang terbaru, Anda dapat menggunakan properti GCSettings.LargeObjectHeapCompactionMode untuk memadatkan tumpukan objek besar sesuai permintaan. Selain itu, LOH secara otomatis akan dipadatkan ketika batas keras ditetapkan dengan menentukan:

Pengumpul sampah menggunakan informasi berikut untuk menentukan hidup atau tidaknya suatu objek:

  • Akar tumpukan: Variabel tumpukan yang disediakan oleh pengkompilasi just-in-time (JIT) dan stack walker. Pengoptimalan JIT dapat memperpanjang atau memperpendek wilayah kode tempat variabel tumpukan dilaporkan ke pengumpul sampah.

  • Pengumpulan sampah menangani: Menangani yang menunjuk ke objek terkelola dan yang dapat dialokasikan oleh kode pengguna atau runtime bahasa umum.

  • Data statis: Objek statis di domain aplikasi yang dapat mereferensikan objek lain. Setiap domain aplikasi melacak objek statiknya.

Sebelum pengumpulan sampah dimulai, semua utas terkelola ditangguhkan kecuali untuk utas yang memicu pengumpulan sampah.

Ilustrasi berikut menunjukkan utas yang memicu pengumpulan sampah dan menyebabkan utas lainnya ditangguhkan:

Screenshot of how a thread triggers a Garbage Collection.

Sumber daya tidak terkelola

Untuk sebagian besar objek yang dibuat aplikasi Anda, Anda dapat mengandalkan pengumpulan sampah untuk melakukan tugas manajemen memori yang diperlukan secara otomatis. Namun, sumber daya yang tidak dikelola memerlukan pembersihan eksplisit. Jenis sumber daya yang tidak dikelola paling umum adalah objek yang membungkus sumber daya sistem operasi, seperti penanganan file, penanganan jendela, atau koneksi jaringan. Meskipun pengumpul sampah dapat melacak masa pakai objek terkelola yang merangkum sumber daya yang tidak dikelola, itu tidak memiliki pengetahuan khusus tentang cara membersihkan sumber daya.

Saat Anda menentukan objek yang merangkum sumber daya yang tidak dikelola, Anda disarankan memberikan kode yang diperlukan untuk membersihkan sumber daya yang tidak dikelola dalam metode Dispose publik. Dengan menyediakan metode Dispose, Anda akan memungkinkan pengguna objek Anda untuk secara eksplisit merilis sumber daya ketika mereka selesai dengan objek. Saat Anda menggunakan objek yang merangkum sumber daya yang tidak dikelola, pastikan untuk memanggil Dispose seperlunya.

Anda juga harus menyediakan cara agar sumber daya yang tidak dikelola dirilis jika konsumen jenis Anda lupa memanggil Dispose. Anda dapat menggunakan handel aman untuk membungkus sumber daya yang tidak dikelola, atau mengambil alih metode Object.Finalize().

Untuk informasi selengkapnya tentang membersihkan sumber daya yang tidak dikelola, lihat Membersihkan sumber daya yang tidak dikelola.

Lihat juga