Dua kata yang paling sering muncul saat membahas .NET adalah compiler dan runtime, tetapi pemula jarang tahu tugas masing-masing secara spesifik. Padahal pemahaman ini menjawab pertanyaan praktis: kenapa memori program kita bisa penuh, kenapa aplikasi .NET bisa berjalan di berbagai sistem operasi, dan siapa yang bertanggung jawab membersihkan objek yang sudah tidak dipakai. Chapter ini membedah dua aktor tersebut beserta contoh yang bisa kita amati sendiri.

Pembagian Tugas: Roslyn dan CLR #

.NET membagi pekerjaan menjadi dua tahap dengan aktor yang berbeda:

  1. Compiler (Roslyn) bekerja pada saat build. Compiler ini menerjemahkan source code C# menjadi bytecode bernama IL (Intermediate Language), yang juga dikenal sebagai CIL. Hasilnya disimpan di dalam file assembly (.dll atau .exe).
  2. Runtime (CLR — Common Language Runtime) bekerja pada saat program berjalan. CLR membaca IL, menerjemahkannya menjadi machine code melalui JIT compiler, mengeksekusinya, dan mengelola memori program.

Model dua tahap ini menjelaskan portabilitas .NET: IL tidak terikat pada satu jenis prosesor, sedangkan JIT compiler menerjemahkan IL menjadi machine code yang sesuai dengan prosesor tempat program dijalankan.

JIT Compiler: Menerjemahkan Saat Dibutuhkan #

Nama just-in-time berasal dari kebijakan kerjanya: CLR tidak menerjemahkan seluruh program sekaligus, melainkan menerjemahkan setiap method pada saat method itu pertama kali dipanggil. Method yang tidak pernah dipanggil tidak pernah diterjemahkan — penghematan yang signifikan untuk program besar.

Setelah diterjemahkan, machine code tersebut disimpan sementara dan dipakai kembali untuk pemanggilan berikutnya. Inilah alasan aplikasi .NET kadang terasa lebih lambat pada detik-detik awal, lalu berjalan mulus setelahnya.

Garbage Collection: Pengelola Memori CLR #

Dalam bahasa seperti C, programmer harus membebaskan memori secara manual. CLR mengambil alih tugas tersebut melalui garbage collection (GC): mekanisme yang melacak objek mana yang masih dirujuk, lalu membebaskan memori objek yang tidak dirujuk lagi.

Kebijakan GC berbasis generation (generasi): objek baru masuk ke generasi 0, dan proses pembersihan paling sering menyasar generasi termuda. Kita bisa mengamati mekanisme ini langsung melalui class GC:

csharp
Console.WriteLine($"Pengumpulan generasi 0 sebelum: {GC.CollectionCount(0)}");

string[] data = new string[1000]; // array besar dialokasikan di memori
data = new string[1];             // data kini menunjuk array baru; array besar menjadi sampah memori
GC.Collect();                     // garbage collection dijalankan

Console.WriteLine($"Pengumpulan generasi 0 sesudah: {GC.CollectionCount(0)}");

Output yang Dihasilkan:

text
Pengumpulan generasi 0 sebelum: 0
Pengumpulan generasi 0 sesudah: 1

Mari kita bedah apa yang terjadi pada setiap baris:

  1. Baris 1 (GC.CollectionCount(0)): method ini melaporkan berapa kali generasi 0 sudah dikumpulkan sejak program dimulai. Pada awal program, angkanya masih 0.
  2. Baris 3 (string[] data = new string[1000];): CLR mengalokasikan array berisi 1000 rujukan string di managed heap, dan variable data menunjuk ke array tersebut.
  3. Baris 4 (data = new string[1];): variable data dialihkan menunjuk array baru yang kecil. Array besar kehilangan semua rujukannya dan menjadi garbage — objek yang tidak lagi dapat dijangkau program.
  4. Baris 5 (GC.Collect();): kita meminta GC berjalan. GC memeriksa objek yang dirujuk, menemukan array besar tanpa rujukan, dan membebaskan memorinya. Counter generasi 0 naik menjadi 1.
  5. Baris 7 (Console.WriteLine(...)): angka 1 mencerminkan satu kali pengumpulan yang baru saja terjadi.

Yang Perlu Diingat #

Roslyn dan CLR bekerja bergantian: compiler menerjemahkan source code menjadi IL pada saat build, runtime mengeksekusi IL dan mengelola memori pada saat program berjalan. Dengan pemahaman ini, kita siap melihat di bidang apa saja program C# benar-benar dipakai — materi Chapter Penggunaan C#.