Bagikan melalui


Dasar-dasar pengumpulan sampah

Dalam CLR (common language runtime), pengumpul sampah (GC) berfungsi sebagai manajer memori otomatis. Pengumpul sampah otomatis mengelola alokasi dan pembebasan memori untuk sebuah aplikasi. 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 heap 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 data tersebut dikonfirmasi.
    Dikomitmenkan 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 dikomitmenkan.

    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 melakukan paging sampai diperlukan, jadi dimungkinkan untuk mengalami paging dalam situasi di mana tekanan pada 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 heap yang dikelola. Heap terkelola menyimpan penunjuk ke alamat di mana objek berikutnya dalam heap akan dialokasikan. Awalnya, penunjuk ini diatur ke alamat dasar heap yang dikelola. Semua jenis referensi dialokasikan di heap terkelola. Ketika aplikasi membuat tipe referensi pertama, memori dialokasikan untuk tipe tersebut di alamat dasar heap 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 menambah nilai ke pointer, prosesnya hampir secepat mengalokasikan memori di stack. Selain itu, karena objek baru yang dialokasikan secara berurutan disimpan secara berdekatan di timbunan terkelola, aplikasi dapat mengakses objek dengan cepat.

Pembebasan 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 field statis, variabel lokal pada tumpukan utas, register CPU, handle GC, dan antrian finalisasi. Setiap akar merujuk pada objek di heap terkelola atau disetel 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 melepaskan memori yang telah dialokasikan untuknya. 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, fungsi penyalinan memori digunakan untuk memadatkan objek yang dapat dijangkau dalam memori, 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 heap terkelola bertahan setelah pengumpulan sampah, maka tidak perlu dilakukan 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 notifikasi memori rendah dari sistem operasi atau kondisi memori rendah 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.

Heap yang dikelola

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. Pengelola sampah juga memesan segmen sesuai kebutuhan dan mengembalikan segmen 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 hidup sehingga mereka dipindahkan ke satu tempat, dan ruang tidak terpakai dihapus, sehingga membuat ukuran heap 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.

Petunjuk / Saran

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

Generasi

Algoritma GC didasarkan pada beberapa pertimbangan:

  • Memadatkan memori pada sebagian timbunan terkelola lebih cepat dilakukan 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.

Pengelolaan sampah terutama terjadi dengan pengambilan 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 dari heap terkelola lebih cepat daripada memadatkan seluruh heap, metode ini memungkinkan pengumpul sampah untuk melepaskan memori dalam generasi tertentu, alih-alih melepaskan memori untuk seluruh heap 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 ditempatkan pada heap 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 tetap ada setelah pengumpulan akan tetap pada generasi 2 sampai dinyatakan tidak dapat dijangkau dalam pengumpulan mendatang.

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

Pengumpulan sampah otomatis terjadi dalam generasi tertentu ketika kondisi terpenuhi. 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).

Keberlangsungan dan promosi jabatan

Objek yang tidak dikelola ulang dalam proses pengelolaan sampah disebut bertahan 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 peningkatan ukuran memori yang diklaim ulang dengan ukuran yang signifikan. 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 standar segmen sementara:

Stasiun kerja/server GC 32-bit 64-bit
Workstation GC 16 MB 256 MB
Server pengumpulan sampah 64 MB 4 GB
Server GC dengan > 4 prosesor logis 32 MB 2 GB
Server GC dengan > 8 prosesor 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 dibebaskan sebanding dengan memori yang ditempati oleh objek yang tidak terpakai.

Hal-hal yang terjadi selama pengumpulan sampah

Pengumpulan sampah memiliki fase-fase berikut:

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

  • Fase relokasi yang memperbarui referensi ke objek yang dikompresi.

  • Fase pemadatan yang mengambil kembali ruang yang ditempati oleh objek mati dan memadatkan objek yang masih ada. Fase pemadatan memindahkan objek yang telah bertahan dari pengumpulan sampah ke arah ujung segmen yang lebih tua.

    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 dipadatkan karena menyalin objek besar mengakibatkan penurunan kinerja. 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.

  • Handle pengumpulan sampah: Handle yang menunjuk ke objek terkelola dan dapat dialokasikan oleh kode pengguna atau runtime bahasa bersama.

  • 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 utas yang memicu pengumpulan sampah.

Ilustrasi berikut menunjukkan benang yang memicu pengumpulan sampah serta menyebabkan benang lainnya ditangguhkan.

Cuplikan layar bagaimana sebuah utas memicu pengumpulan sampah otomatis.

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 pengguna tipe Anda lupa memanggil Dispose. Anda dapat menggunakan pegangan aman untuk membungkus sumber daya yang tidak dikelola, atau mengganti metode Object.Finalize().

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

Lihat juga