← Blog'a Dön

Garbage Collector Nasıl Çalışır?

Garbage Collector Nasıl Çalışır?
Belleği Sizin Yerinize Temizleyen Sessiz Görevli
C ya da C++ gibi dillerde belleği kendiniz yönetirsiniz. Nesne oluşturdunuz mu, bellekte yer ayırdınız. İşiniz bitti mi, o belleği kendiniz serbest bırakırsınız. Bırakmayı unutursanız memory leak, yanlış zamanda bırakırsanız uygulama çöküşü.
Kotlin ve Java bu sorumluluğu sizden alır ve Garbage Collector'a (GC) devreder. GC, artık ihtiyaç duyulmayan nesneleri otomatik olarak tespit eder ve bellekten temizler. Ama bu işi nasıl yapar? Ve bu işin bir bedeli var mı?

GC'nin Temel Sorusu: Bu Nesne Hâlâ Gerekli Mi?
GC'nin yaptığı işin özü son derece basit bir soruya dayanır: Bu nesneye uygulamanın geri kalanından hâlâ ulaşılabilir mi?
Bir nesneye ulaşılabilmesi için o nesneye uzanan en az bir referans zincirinin var olması gerekir. Bu zincirin başlangıç noktalarına GC Root denir. GC Root'lar arasında aktif thread'ler, statik değişkenler ve JNI referansları yer alır.
GC, bu kök noktalardan başlayarak tüm referans zincirlerini takip eder. Ulaşabildiği her nesne "canlı" sayılır ve dokunulmaz. Hiçbir zincirden ulaşılamayan nesneler ise "ölü" sayılır ve temizlenmek üzere işaretlenir.

Heap: Nesnelerin Yaşadığı Alan
Android uygulamalarında nesneler heap adı verilen bellek bölgesinde yaşar. ART, heap'i farklı bölgelere ayırarak nesneleri yaşam sürelerine göre organize eder.
Young Generation (Genç Nesil) yeni oluşturulan nesnelerin ilk yerleştiği bölgedir. Araştırmalar göstermiştir ki çoğu nesne son derece kısa ömürlüdür — bir döngü değişkeni, geçici bir hesaplama sonucu, kısa süreli bir callback. Bu nesneler Young Generation'da hızlıca temizlenir.
Old Generation (Eski Nesil) ise birden fazla GC döngüsünden sağ çıkmayı başaran nesnelerin taşındığı bölgedir. Uzun ömürlü olduğu kanıtlanmış nesneler burada yaşar. Bu bölgenin temizlenmesi daha nadir ama daha kapsamlı gerçekleşir.
Bu ayrım GC'yi verimli kılar. Her temizlik döngüsünde tüm heap'i taramak yerine, önce en çok nesne biriken ve en hızlı dolup taşan Young Generation taranır.

Mark and Sweep: İşaretleme ve Temizleme
GC'nin kullandığı temel algoritma Mark and Sweep olarak adlandırılır ve iki aşamadan oluşur.
İşaretleme aşamasında GC, GC Root'lardan başlayarak ulaşabildiği tüm nesneleri "canlı" olarak işaretler. Bu aşama tüm referans ağını dolaşmayı gerektirir.
Temizleme aşamasında işaretlenmemiş, yani ulaşılamayan tüm nesnelerin kapladığı bellek alanı serbest bırakılır.
Bazı GC implementasyonları bu adıma Compact aşamasını da ekler. Temizlik sonrasında bellekte dağınık boşluklar kalabilir. Compaction bu boşlukları ortadan kaldırarak canlı nesneleri bir araya toplar ve bellek kullanımını daha verimli hale getirir.

GC'nin Bedeli: Stop-the-World
GC'nin en tartışmalı özelliği Stop-the-World duraklamasıdır.
Bazı GC aşamaları çalışırken uygulama thread'lerinin durdurulması gerekir. Çünkü uygulama çalışmaya devam ederken referans yapısı değişebilir ve GC tutarsız bir tablo üzerinde çalışmış olur. Bu duraklamayı önlemek için GC çalışırken her şeyi geçici olarak dondurur.
Eski Android sürümlerinde bu duraklamalar kullanıcı tarafından fark edilebilecek kadar uzun sürebiliyordu. ART bu sorunu önemli ölçüde azalttı. Modern ART, concurrent GC yaklaşımını benimseyerek büyük bölümü uygulama çalışırken arka planda tamamlar, Stop-the-World süresini minimum düzeye indirir.
Yine de GC duraklamaları tamamen ortadan kalkmış değildir. Özellikle Young Generation'ın hızla dolup taştığı durumlarda — çok fazla geçici nesne üretildiğinde — GC sıklaşır ve bu duraklamalar birikerek gözlemlenebilir bir performans düşüşüne dönüşür.

Fazla Nesne Üretmenin Tehlikesi
GC'yi en çok zorlayan şey sık ve gereksiz nesne üretimidir. Her new ifadesi heap'te yer kaplar. Young Generation dolduğunda GC tetiklenir. GC sık tetiklenirse duraklamalar birikerek jank yaratır.
Bu sorun özellikle döngüler içinde ve animasyon callback'lerinde kritik hale gelir. Saniyede 60 kez çağrılan bir onDraw metodunun içinde nesne üretmek, GC'yi sürekli meşgul eder.
Çözüm nesneleri döngü dışında oluşturmak, mümkün olduğunda yeniden kullanmak ve gerektiğinde nesne havuzları (object pool) kullanmaktır.

Geliştirici Perspektifinden Bakış
GC'yi anlamak bellek yönetimine bakış açınızı değiştirir.
GC'yi tamamen kontrolünüz dışında bir mekanizma olarak değil, birlikte çalışmanız gereken bir ortak olarak görün. Kısa ömürlü nesneleri hızla üretip bırakmak GC'nin işidir — ama bu döngüyü aşırı hızlandırmak performansa dokunur. Uzun ömürlü ama gereksiz nesneleri tutmak GC'nin işi değildir — bu sizin sorununuzdur.
Android Studio'nun Memory Profiler'ı GC olaylarını gerçek zamanlı gösterir. Animasyon sırasında ya da yoğun kullanım anlarında GC'nin ne sıklıkla tetiklendiğini izlemek, uygulamanızın bellek profilini anlamanın en doğrudan yoludur.