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. Bagi pengembang yang bekerja dengan kode terkelola, ini berarti Anda tidak perlu menulis kode untuk melakukan tugas manajemen memori. Manajemen memori otomatis dapat menghilangkan masalah umum, seperti lupa untuk membebaskan objek dan menyebabkan kebocoran memori, atau mencoba untuk mengakses memori bagi objek yang telah 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.

  • Menyediakan keamanan memori dengan memastikan bahwa objek tidak dapat menggunakan untuknya 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 paging 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 hingga blok memori tersebut diterapkan.
    Diterapkan Blok memori ditetapkan ke penyimpanan fisik.
  • Ruang alamat virtual dapat terfragmentasi. Ini berarti bahwa ada blok gratis, yang juga 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 tersebut. 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 paging digunakan bahkan jika tekanan memori fisik (yaitu, permintaan memori fisik) rendah. Saat tekanan memori fisik tinggi untuk pertama kali, sistem operasi harus membuat ruang dalam memori fisik untuk menyimpan data, dan mencadangkan beberapa data yang berada di dalam memori fisik ke file paging. Data tersebut tidak akan dimasukkan ke file paging sampai data diperlukan sehingga memungkinkan dilakukan paging dalam situasi saat tekanan memori fisik rendah.

Alokasi Memori

Saat Anda menginisialisasi proses baru, runtime akan mencadangkan 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, pengumpul sampah mengalokasikan memori untuknya di ruang alamat segera setelah objek pertama. Selama ruang alamat tersedia, pengumpul sampah secara terus menerus mengalokasikan ruang untuk objek baru dengan cara ini.

Mengalokasikan memori dari timbunan 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. Dengan menggunakan daftar ini, pengumpul sampah 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 timbunan terkelola, mencari blok ruang alamat yang ditempati oleh objek 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. Pengumpul sampah juga mengatur posisi penunjuk timbunan 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 timbunan terkelola bertahan dalam pengumpulan, pemadatan memori tidak diperlukan.

Untuk meningkatkan performa, runtime mengalokasikan memori untuk objek besar dalam timbunan terpisah. Pengumpul sampah secara otomatis akan melepaskan memori untuk objek 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. Hal ini akan terdeteksi oleh pemberitahuan memori rendah dari OS 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. Di kebanyakan kasus, Anda tidak perlu memanggil metode ini, karena pengumpul sampah berjalan secara terus menerus. Metode ini terutama digunakan untuk situasi dan pengujian unik.

Timbunan terkelola

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

Terdapat timbunan 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 mencadangkan 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 mengeklaim kembali akan memadatkan objek hidup sehingga objek dapat dipindahkan bersama, serta ruang mati dapat dihapus, sehingga akan membuat timbunan menjadi lebih kecil. Hal ini memastikan bahwa objek yang dialokasikan bersama-sama akan tetap bersama di timbunan terkelola untuk mempertahankan lokalitasnya.

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. Objek instans dengan ukuran yang sangat besar jarang ditemukan.

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 objek berukuran besar, objek akan masuk ke timbunan objek besar (LOH), yang kadang kala 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 saat generasi 0 penuh, pengumpul sampah akan melakukan pengumpulan dalam upaya 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 memori yang diklaim kembali dalam pengumpulan generasi 0 tidak cukup untuk memungkinkan aplikasi membuat objek baru, pengumpul sampah dapat melakukan pengumpulan generasi 1, kemudian dilanjutkan 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 pengumpulan akan tetap berada di generasi 2 sampai objek tersebut ditetapkan sebagai objek yang tidak terjangkau di pengumpulan masa mendatang.

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

Pengumpulan sampah terjadi pada 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 pengumpulan ini mengeklaim kembali objek di semua generasi (yaitu, semua objek dalam timbunan terkelola).

Bertahan dan promosi

Objek yang tidak diklaim ulang 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 ephemeral bervariasi bergantung pada sistemnya, baik itu 32-bit atau 64-bit, dan bergantung pada jenis pengumpul sampah yang dijalankannya (tempat kerja atau server GC). Tabel berikut ini menunjukkan 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 oleh proses Anda dan memorinya yang memungkinkan).

Jumlah memori yang dibebaskan dari pengumpulan sampah ephemeral terbatas pada ukuran segmen ephemeral. Jumlah memori yang dibebaskan 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 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 objek-objek ini dipromosikan ke generasi 2.

    Biasanya, timbunan objek besar (LOH) tidak dipadatkan, karena menyalin objek besar akan dikenakan 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 pengompilasi just-in-time (JIT) dan walker tumpukan. Pengoptimalan JIT dapat memperpanjang atau memperpendek wilayah kode tempat variabel tumpukan dilaporkan ke pengumpul sampah.

  • Penanganan pengumpulan sampah. Penanganan yang menunjuk objek terkelola dan yang dapat dialokasikan oleh kode pengguna atau oleh runtime bahasa umum.

  • Data statik. Objek statik dalam 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.

When a thread triggers a Garbage Collection

Sumber daya tidak terkelola

Untuk sebagian besar objek yang dibuat oleh aplikasi Anda, Anda dapat mengandalkan pengumpulan sampah untuk secara otomatis menjalankan tugas manajemen memori yang diperlukan. 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, ia 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