Bellek sızıntısı: nedir, tipik senaryolar ve teşhis

Yazar: IT Sectr Yayınlanma: 2026-07-29 Okuma süresi: 10 dk

Bellek sızıntısı (memory leak) — bir uygulamanın artık ihtiyaç duyulmayan nesnelerin kapladığı belleği serbest bırakmaması durumudur. Mobil geliştirmede bu özellikle kritiktir: sınırlı heap ve swap eksikliği OutOfMemoryError ve uygulama çökmesine yol açar. Purdue University'ye (2022) göre, Google Play'deki Android uygulamalarının %35'i en az bir bellek sızıntısı içerir. Tipik senaryoları, teşhis araçlarını ve düzeltme yöntemlerini inceleyelim.

Önemli Noktalar

  • GC Root — çöp toplayıcının canlı nesneleri belirlediği giriş noktası
  • Context sızıntısı — singleton'a Activity Context aktarmak tüm View hiyerarşisini tutar
  • Handler postDelayed ile — Activity yok edilirse, Handler GC'ye gitmesini engeller
  • Heap dump — MAT veya Android Profiler ile sızıntı analizinin ana yöntemi
  • SoftReference — bellek yetersizliğinde otomatik temizlenen önbellekler için WeakReference alternatifi

Mobil uygulamalarda bellek sızıntısı nedir?

Bellek sızıntısı — ayrılan belleğin, nesne program tarafından artık ihtiyaç duyulmadıktan sonra sisteme geri verilmemesi durumudur. Çöp toplayıcı, GC Root'tan ona işaret eden aktif bir referans zinciri olduğu için böyle bir nesneyi canlı olarak kabul eder.

Java/Kotlin'de çöp toplayıcı otomatik olarak çalışır, ancak teknik bir referans varsa bir nesnenin mantıksal olarak gereksiz olduğunu belirleyemez. Geliştirici gereksiz bağlantıları açıkça koparmalıdır. Swift/Objective-C'de ARC otomatik olarak referansları sayar, ancak retain cycle'lar sayacın sıfıra düşmesini engeller.

Sızıntıların ana tehlikesi kümülatif etkidir. Her sızıntı az miktarda bellek tüketir, ancak tekrarlanan ekran geçişleriyle (ekran döndürme, Activity açma/kapama) sızıntılar heap sınırı tükenene kadar birikir.

Sızıntı şişkinlikten nasıl farklıdır?

Sızıntı — nesne koddan erişilemez ancak GC tarafından kaldırılmamıştır. Şişkinlik — nesne mantıksal olarak gereklidir ancak aşırı miktarda depolanır. Şişkinlik örneği: 30 MB çalışma setine sahip 100 MB görüntü önbelleği. Her iki sorun da OOM'ye yol açar, ancak nedenler ve tedavi yöntemleri farklıdır.

Çöp toplayıcı nasıl çalışır ve sızıntılar neden oluşur?

ART (Android Runtime) eşzamanlı sıkıştırma ile nesil bazlı çöp toplama kullanır. Bellek Genç (Young), Yaşlı (Old) ve Büyük nesneler (Large) olarak ayrılır. Birkaç GC döngüsünden kurtulan nesneler, toplamanın daha az sıklıkta olduğu Old nesline taşınır — bu normal döngüleri hızlandırır.

GC, heap belirli bir doluluk eşiğine (genellikle %75-85) ulaştığında başlar. GC sırasında, uygulamanın tüm iş parçacıkları duraklatılır (STW — Stop The World). Ne kadar çok canlı nesne varsa, duraklama o kadar uzun olur. Sızıntılar canlı nesne sayısını artırarak GC duraklamalarını uzatır.

Toplayıcı, GC Roots'tan grafiği dolaşarak canlı nesneleri belirler: statik alanlar, aktif iş parçacıklarının yığın değişkenleri, JNI referansları. Bu köklerden referanslarla ulaşılabilen herhangi bir nesne canlı olarak kabul edilir — geliştirici artık ihtiyaç olmadığını bilse bile.

kotlin
// Örnek: GC Root olarak statik koleksiyon — kalıcı sızıntı
object GlobalHolder {
    val listeners = mutableListOf<WeakReference<Any>>()
}

class LeakingFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        GlobalHolder.listeners.add(WeakReference(this))
        // WeakReference GC'yi engellemez — doğru davranış
    }
}

WeakReference sorunu çözer: GC canlı nesneleri belirlerken zayıf referansları yok sayar. Bir nesneye yalnızca zayıf referanslar kaldıysa, en yakın GC döngüsünde toplanacaktır.

Android ve iOS'ta tipik sızıntı senaryoları

Activity Context — Android'de en yaygın sızıntı senaryosu. Bir singleton, statik alan veya uzun ömürlü hizmet bir Activity Context referansı saklarsa, tüm Views ile birlikte Activity'nin tamamı GC tarafından toplanamaz. Çözüm: uzun ömürlü nesneler için Application Context kullanın.

Handler ve gönderilen mesajlar — Handler.postDelayed(runnable, delay) Main Looper kuyruğuna bir mesaj koyar. Gecikme süresi dolmadan Activity yok edilirse, mesaj hala kuyruktadır ve Runnable → anonim sınıf → dış sınıf (Activity) aracılığıyla bir referans tutar.

kotlin
class SafeActivity : AppCompatActivity() {
    private val mainHandler = Handler(Looper.getMainLooper())
    private val callback = Runnable { /* update UI */ }

    override fun onResume() {
        super.onResume()
        mainHandler.postDelayed(callback, 5000)
    }

    override fun onPause() {
        mainHandler.removeCallbacks(callback) // zorunlu: kuyruğu temizle
        super.onPause()
    }
}

İç sınıflar — statik olmayan bir iç sınıf, dış sınıf örneğine örtük bir referansa sahiptir. Dış sınıf bir Activity ise ve iç sınıf dışarıya bir yere (örneğin, RecyclerView.Adapter'a) aktarılırsa, Activity toplanamaz.

  • TimerTask ve ScheduledExecutorService — Activity yok edilmeden önce planlanan görevler
  • BroadcastReceiver — onPause/onDestroy'da kaydı silinmezse Context'i tutmaya devam eder
  • View referansı ile ViewModel — ViewModel Activity'den daha uzun yaşar, View referansı sızıntıya yol açar
  • Retrofit Call — Call iptal edilmezse, yanıt yok edilmiş Fragment'e gelir

Bellek sızıntısı teşhis araçları

Android Studio Memory Profiler — gerçek zamanlı heap izleme için yerleşik araç. Kullanılan bellek grafiğini, ayırma sayısını ve türe göre nesneleri gösterir. Heap dump kaydetmeye ve MAT'te analiz için HPROF formatında dışa aktarmaya olanak tanır.

Eclipse MAT (Memory Analyzer Tool) — masaüstü heap dump analizörü. Otomatik olarak Leak Suspects raporları oluşturur, en büyük retained size'a sahip nesneleri vurgular ve her şüpheli nesne için olası GC root chain'ini önerir.

Xcode Memory Graph Debugger — iOS için. Uygulamayı durdurur ve nesne grafiğini görselleştirir. Retain cycle'lar kırmızıyla vurgulanır; herhangi bir nesneye tıklayarak retain count ve referanslarını görebilirsiniz.

AraçYeteneklerKarmaşıklık
Memory ProfilerGerçek zamanlı grafik, heap dump, Object Allocation TrackingDüşük
Eclipse MATDominator tree, Leak Suspects, OQL sorgularıOrta
LeakCanaryOtomatik algılama, bildirimde sızıntı iziMinimum
Xcode Memory GraphGörsel retain cycle grafiği, canlı nesne listesiDüşük

Uber Engineering Blog'a göre, CI/CD hattına otomatik bellek profillemesi (LeakCanary + heap dump analizi) entegre etmek, 3 ay içinde üretimdeki bellek ile ilgili olayları %60 azaltır.

Sızıntıları giderme yöntemleri

Context'i değiştirin — bir nesne Activity'den daha uzun yaşıyorsa, applicationContext kullanın. Tüm uzun ömürlü nesneler (singleton'lar, havuzlar, veritabanı yardımcıları) Activity Context değil, Application Context almalıdır. İstisna: Activity'ye özgü tema veya kaynaklara erişmesi gereken UI bileşenleri.

Lifecycle-aware bileşenler — LifecycleObserver, DefaultLifecycleObserver veya reaktif uzantılar kullanmak, onDestroy'da abonelikleri otomatik olarak iptal eder. Android Jetpack, ilgili yaşam döngüsü olayı tarafından temizlenen lifecycleScope ve viewModelScope'u sağlar.

Statik iç sınıf — iç sınıfın dış sınıfın alanlarına erişmesi gerekmiyorsa, onu static yapın. Statik bir iç sınıfın dış sınıfa örtük bir referansı yoktur. Erişim gerekiyorsa, açık referans için WeakReference kullanın.

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ Statik olmayan iç sınıf — MyActivity'ye örtük referans
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ Statik iç sınıf — örtük referans yok
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

iOS'ta yakalama listeleri kullanın: yaratıcısından daha uzun yaşayabilen closure'larda [weak self]. Temsilciler için zayıf referanslar kullanın (weak var delegate). Yalnızca self'in yaşam süresi boyunca çağrılacağı garanti edilen closure'lar için [unowned self] kullanılabilir, ancak dikkatli olun — serbest bırakılmış bir nesneye erişim çökmeye neden olur.

Sıkça Sorulan Sorular

Özel araçlar olmadan sızıntı nasıl bulunur?

Android'de birkaç ekran geçişi yapın (Activity A → B → A → B) ve adb shell dumpsys meminfo package_name komutunu kontrol edin. Total PSS istikrarlı bir şekilde büyüyor ve orijinal değere dönmüyorsa — sızıntı var. iOS'ta benzer şekilde: görsel inceleme için Xcode'da Debug Memory Graph kullanın.

Bir Kotlin coroutine sızıntıya neden olabilir mi?

Evet, bileşen yok edildiğinde CoroutineScope iptal edilmezse. GlobalScope'da başlatılan bir coroutine, Activity'nin finish()'inden sonra bile çalışmaya devam eder. Çözüm: viewModelScope (onCleared'da iptal edilir) veya lifecycleScope (onDestroy'da iptal edilir) kullanın. Özel kapsamlar için LifecycleOwner aracılığıyla lifecycle-aware kapsamlar oluşturun.

Bitmap sızıntıları nasıl etkiler?

Bitmap piksel verilerini Java heap'inde değil, yerel heap'te (native heap) depolar. Bu, Java GC'nin Bitmap'in gerçek boyutunu görmediği anlamına gelir. Bitmap üzerinde recycle() çağrılmazsa veya referans null yapılmazsa, yerel bellek serbest bırakılmaz. Küçültülmüş kopyaları yüklemek için BitmapFactory'yi inSampleSize ile ve otomatik önbellek yönetimi için Glide/Coil kullanın.

Statik alan üzerinden sızıntı nedir?

Statik alan bir GC Root'tur. Sınıf yüklü olduğu sürece yaşar (Android'de — Process canlı olduğu sürece). Statik bir alan bir Activity, Bitmap, View veya başka bir ağır nesneye referans veriyorsa, o nesne asla GC tarafından toplanmayacaktır. Statik alan sonsuz bir referanstır. Çözüm: yalnızca WeakReference saklayın veya onDestroy'da statik alanı null yapın.

ARC ile iOS'ta sızıntılardan nasıl kaçınılır?

ARC, güçlü referans sayacı sıfıra düştüğünde nesneleri otomatik olarak serbest bırakır. ARC altında sızıntının tek yolu retain cycle'dır. Çocuğun ebeveynden daha uzun yaşayabileceği ebeveyn→çocuk referansları (temsilciler, veri kaynakları) için her zaman weak kullanın. Closure'lar için yakalama listesi [weak self] kullanın ve closure içinde self'in nil olup olmadığını kontrol edin.

Özet

  • Bellek sızıntısı — nesne koddan erişilemez ancak GC Root'tan aktif bir referans olduğu için GC tarafından kaldırılmaz
  • GC Roots statik alanlar, yığın değişkenleri ve JNI referanslarını içerir; bunlardan ulaşılabilen her nesne canlıdır
  • Context sızıntısı — Android'de en yaygın sorun: singleton veya statik alana Activity Context aktarımı
  • Handler ve iç sınıf — ikinci en yaygın neden: Looper kuyruğundaki iptal edilmemiş mesajlar Activity referansını tutar
  • LeakCanary — standart otomatik algılama aracı; heap dump alır ve tam GC root chain'ini gösterir
  • lifecycleScope ve viewModelScope coroutine'ler yoluyla sızıntı sorununu çözer — yok etmede otomatik iptal
  • CI/CD'de belleği profilleme: hata ayıklamada LeakCanary + test çalıştırmalarında heap dump analizi, yeni sızıntılarda birleştirmeyi engellemelidir

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun