---
title: 'Bellek Yönetimi'
source: 'https://academia.sh/tr/kurslar/android-uretim/bellek-yonetimi'
course: 'Test, Başarım ve Dağıtım'
language: tr
updated: '2026-10-21T05:02:43+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Bellek sızıntısını, tutulan nesneyi ve zayıf referansı tanımlar'
    - 'Kaydı kaldırılmayan bir dinleyicinin her açma ve kapama döngüsünde bir ekranı tuttuğunu modelden ölçer'
    - 'Aynı sızıntının bütçeye göre içinde, azında, çoğunda ya da hepsinde çıkabildiğini gösterir'
    - 'Dinleyiciyi yaşam döngüsüne bağlamanın sonraki döngülerin tutulan nesne sayısını artırmadığını, önceki sızıntıyı silmediğini ölçer'
---

# Bellek Yönetimi

Kapanan bir ekranın bellekte kalması bellek sızıntısıdır. Ders kaydı kaldırılmayan bir dinleyicinin her döngüde bir ekranı tuttuğunu, tutulan nesne sayısının bütçeyi kaç döngüde aştığını ve aynı sızıntının bütçeye göre kategori değiştirdiğini ölçer.

Önceki derste kaydırma kare bütçesine sığdı, ama her kare belleğe nesne bırakır ve bu nesnelerin bir kısmı işi bittiğinde serbest kalmaz. Bellek sorunu çizim sorunundan farklı görünür. Takılma anında fark edilir; bellek ise sessizce dolar ve uygulama ancak platform onu sonlandırdığında ya da bellek yetmediğinde fark edilir. Bu yüzden ölçümü bir kare yerine bir döngüye bağlıyoruz: ekranı açıp kapatmak bir döngüdür ve her döngünün sonunda bellekte kaç nesne kaldığına bakılır.

Sorunun adı **bellek sızıntısı**dır (memory leak). Çöp toplama, kimsenin erişemediği nesneyi serbest bırakır. Bir nesneye hâlâ erişen bir yol varsa, yani başka bir nesne ona referans veriyorsa, o nesne işi bitmiş olsa da serbest kalmaz. Bu yolla bellekte kalan nesneye **tutulan nesne** (retained object) diyoruz. Sızıntı, tutulan nesnelerin döngü başına biriktiği durumdur.

Ölçü yine kursun ölçüsüdür. Tutulan nesne sayısının bütçesi kaç döngünün sonunda aşıldı.

```kotlin
// Overrun.kt — bir olcumun butcesini kac ornekte astigini siniflandirir
class Budget(val name: String, val limit: Int)

class Overrun(val label: String, val budget: Budget, samples: List<Int>) {
    val total: Int = samples.size
    val over: Int = samples.count { it > budget.limit }
    val verdict: String = when {
        over == 0 -> "icinde"
        over == total -> "hepsinde"
        over * 2 < total -> "azinda"
        else -> "cogunda"
    }

    fun line() {
        println("$label: butce=${budget.name}<=${budget.limit} | asan=$over/$total -> $verdict")
    }
}

fun countOverruns(overruns: List<Overrun>) {
    for (name in listOf("icinde", "azinda", "cogunda", "hepsinde")) {
        println("$name: ${overruns.count { it.verdict == name }}")
    }
}
```

Bu dosya kursun her dersinde aynıdır. `Budget` ölçümün adını ve sınırını taşır, `Overrun` aşan örnekleri sayıp dört aşımdan birini verir.

## Kaydı Kaldırılmayan Dinleyici

Sızıntının en yaygın biçimi şudur: uzun yaşayan bir nesne, kısa yaşayan bir nesneye referans tutar. Uygulama boyunca yaşayan bir veri kaynağı olsun. Ekran açıldığında kendini bu kaynağa dinleyici olarak kaydeder, çünkü güncellemeleri almak ister. Ekran kapandığında kaydını kaldırmazsa kaynak, ekranı dinleyici listesinde tutmaya devam eder. Ekranın işi bitmiştir ama ona giden yol sürmektedir; çöp toplama onu serbest bırakamaz.

```kotlin
// Retention.kt — kaydi kaldirilmayan dinleyici kapanan ekrani bellekte tutar
class Screen(val id: Int) {
    var closed: Boolean = false
}

class Source {
    private val strong = mutableListOf<Screen>()
    private val weak = mutableListOf<Screen>()

    fun register(screen: Screen) {
        strong.add(screen)
    }

    fun registerWeak(screen: Screen) {
        weak.add(screen)
    }

    fun unregister(screen: Screen) {
        strong.remove(screen)
        weak.remove(screen)
    }

    // zayif referans kapanan ekrani tutmaz; guclu referans tutar
    fun retainedObjects(): Int = strong.size + weak.count { !it.closed }
}

fun cycle(source: Source, id: Int, how: String): Int {
    val screen = Screen(id)
    when (how) {
        "guclu" -> source.register(screen)
        "zayif" -> source.registerWeak(screen)
        "yasam" -> {
            source.register(screen)
            source.unregister(screen)
        }
    }
    screen.closed = true
    return source.retainedObjects()
}
```

`Source` uygulama boyunca yaşayan kaynaktır. `register` güçlü bir referansla, `registerWeak` zayıf bir referansla kayıt tutar. `cycle` bir döngüdür: ekran açılır, kaynağa kaydolur, kapanır ve kaynağın o anda kaç ekran tuttuğu döner. `how` kaydın nasıl yapıldığını söyler. Önce kaydı hiç kaldırmayan, yani `guclu` olan biçimi sekiz döngü boyunca çalıştıralım. Bütçe üç nesne olsun.

```kotlin
// Main.kt — kaydi kaldirilmayan dinleyici sekiz dongu boyunca ekran biriktirir
fun main() {
    val source = Source()
    val samples = (1..8).map { cycle(source, it, "guclu") }
    println("tutulan: $samples")

    val overrun = Overrun("sizinti", Budget("tutulan", 3), samples)
    overrun.line()
    countOverruns(listOf(overrun))
}
```

```text
tutulan: [1, 2, 3, 4, 5, 6, 7, 8]
sizinti: butce=tutulan<=3 | asan=5/8 -> cogunda
icinde: 0
azinda: 0
cogunda: 1
hepsinde: 0
```

Her döngü bir ekran bıraktı ve tutulan nesne sayısı birer birer arttı. İlk üç döngü bütçenin içinde kaldı, dördüncüden sekizinciye kadar beş döngü aştı: 5/8, yani **çoğunda**. Sızıntının doğası burada görünür: bir döngüde fark edilmez, ama biriken sayı er ya da geç her bütçeyi geçer. Bu yüzden sızıntı, bir tek döngüye bakarak değil, döngüler boyunca ölçülür.

**BA1.** Modelde her ekran bir nesnedir ve her sızan döngü tam bir nesne bırakır. Gerçek bir ekran, kendisinin yanında görünümlerini, görsellerini ve veri yapılarını da tutar; tutulan belleğin boyutu ekrandan ekrana çok değişir. Model ekranı sayar; tuttuğu belleğin boyutunu kurmaz.

## Bütçe Sızıntıyı Gizler

Aynı sızıntıyı dört farklı bütçeye vuralım. Sızıntı bir kez ölçülür, örnekler aynıdır; yalnız sınır değişir.

```kotlin
// Main.kt — ayni sizinti dort butceyle olculur
fun main() {
    val source = Source()
    val samples = (1..8).map { cycle(source, it, "guclu") }

    val overruns = listOf(0, 3, 6, 8).map { limit ->
        Overrun("sinir-$limit", Budget("tutulan", limit), samples)
    }
    overruns.forEach { it.line() }
    countOverruns(overruns)
}
```

```text
sinir-0: butce=tutulan<=0 | asan=8/8 -> hepsinde
sinir-3: butce=tutulan<=3 | asan=5/8 -> cogunda
sinir-6: butce=tutulan<=6 | asan=2/8 -> azinda
sinir-8: butce=tutulan<=8 | asan=0/8 -> icinde
icinde: 1
azinda: 1
cogunda: 1
hepsinde: 1
```

Dört satır, dört aşım ve tek bir sızıntı. Sıfır nesne bütçesinde her döngü aştı. Sekiz nesne bütçesinde hiçbir döngü aşmadı, çünkü sekizinci döngüde tutulan sayı sekizdi ve sınıra eşit örnek aşım sayılmaz. Sızıntı sekiz nesnede de oradaydı; bütçe onu görünmez kıldı. Bu, kuralın zayıf yanıdır: bütçe sızıntıdan geniş seçilirse sızıntı **içinde** diye raporlanır. Bu yüzden sızıntı bütçesi, döngü sayısına göre değil, uygulamanın hiç tutmaması gereken sayıya göre konur. Sızıntıyı yakalayan bütçe çoğu zaman sıfıra ya da sıfıra yakın bir sayıya dayanır.

Platformda tutulan ekranları yakalamak için sanal makine denetimi ve yığın dökümü kullanılır. Aşağıdaki parça ekranların sızmasını günlüğe yazdıran bir denetim kurar ve bir yığın dökümü alan işlevi gösterir.

```kotlin
// Platform kodu: sizinti denetimi ve yigin dokumu
import android.app.Application
import android.os.Debug
import android.os.StrictMode

class SampleApp : Application() {
    override fun onCreate() {
        super.onCreate()
        StrictMode.setVmPolicy(
            StrictMode.VmPolicy.Builder()
                .detectActivityLeaks()
                .detectLeakedRegistrationObjects()
                .penaltyLog()
                .build()
        )
    }
}

fun dumpHeap(path: String) {
    Debug.dumpHprofData(path)
}
```

Bu parça platform kodudur ve burada çıktı, süre ya da dosya yolu iddia etmiyoruz. `StrictMode` sızan ekranları ve kaydı kaldırılmayan yayın alıcılarıyla servis bağlantılarını günlüğe yazdırır; uygulamanın kendi dinleyicileri bu denetime girmez; yığın dökümü ise hangi nesnenin hangi yolla tutulduğunu geliştirme ortamının profil aracında gösterir. Model, bu ölçümün kuralını kurar; model aracın kendisi değildir. Dökümün neyi göstereceği, yani tutulan yolun hangi nesneden geçtiği, modelin göstermediği bilgidir.

## Zayıf Referans

Sızıntıyı çözmenin bir yolu, kaynağın ekranı güçlü değil zayıf bir referansla tutmasıdır. **Zayıf referans** (weak reference) çöp toplamayı engellemez: nesneye başka güçlü yol kalmadıysa nesne serbest bırakılır ve zayıf referans boşalır. Modelde bunu kapanan ekranın artık sayılmaması diye kuruyoruz.

```kotlin
// Main.kt — zayif referansla kaydolan ekran kapaninca tutulmaz
fun main() {
    val source = Source()
    val samples = (1..8).map { cycle(source, it, "zayif") }
    println("tutulan: $samples")

    val overrun = Overrun("zayif", Budget("tutulan", 3), samples)
    overrun.line()
    countOverruns(listOf(overrun))
}
```

```text
tutulan: [0, 0, 0, 0, 0, 0, 0, 0]
zayif: butce=tutulan<=3 | asan=0/8 -> icinde
icinde: 1
azinda: 0
cogunda: 0
hepsinde: 0
```

**BA2.** Modelde zayıf referans kapanan ekranı anında bırakır. Gerçekte çöp toplama bir an gelince çalışır; ekran kapandıktan sonra bir süre, toplama gerçekleşene kadar bellekte durur ve bu süre uygulamadan uygulamaya değişir. Modelin anındalığı bir basitleştirmedir. Ayrıca zayıf referans, kaydı kaldırmanın yerini tutmaz: kaynak, boşalmış kayıtları listesinde taşımaya devam edebilir ve bu da küçük de olsa bir birikimdir.

## Neden Döngü Boyunca Ölçeriz

Bellek sorunları anlık değil birikimlidir. Tek bir ekranı açıp kapatmak, tutulan nesne sayısını bir artırır ya da hiç artırmaz; bu fark tek bir gözlemde bütçenin altında kalır. Sızıntı, aynı işlemin tekrar tekrar yapılmasıyla görünür olur. Bu yüzden ölçümün örnekleri, art arda yapılan döngülerin sonunda alınan tutulan nesne sayılarıdır ve her örnek bir öncekini içerir.

Bu yapı kuralın okunuşunu da etkiler. Birikimli bir dizide aşım, genellikle dizinin sonuna doğru yoğunlaşır: ilk döngüler içinde kalır, sonrakiler aşar. Dersin ilk ölçümünde aşan döngüler dördüncüden sekizinciye kadardı. Bu yüzden döngü sayısını artırmak sızıntıyı daha görünür kılar; döngü sayısını azaltmak sızıntıyı gizler.

Ölçüm tasarımında bir ayrıntı daha önemlidir: her döngü aynı koşulda başlamalıdır. Bir önceki döngüden kalan durum sonrakini etkilerse, aşım sızıntıdan değil ölçüm düzeninden gelebilir. Bu yüzden modelde her döngü aynı `cycle` işleviyle ve aynı kaynak üzerinde çalışır; farklı olan tek şey kaydın nasıl yapıldığıdır.

Tutulan nesnenin ne olduğu da açık söylenmelidir. Ekran bir nesnedir, ama onu tutan her şey onunla birlikte kalır: ekranın görünümleri, verisi, kullandığı yardımcılar. Bir sızıntıyı bulmak bu yüzden tutulan nesnenin kendisine değil, onu tutan yola bakmayı gerektirir. Yığın dökümü tam olarak bu yolu gösterir.

Sızıntıyı aramak için üç soru sırayla sorulur. Hangi nesne tutuluyor, onu hangi yol tutuyor ve bu yol ne zaman kesilmeliydi. İlk soruya sayaç cevap verir, ikinciye yığın dökümü, üçüncüye uygulamanın yaşam döngüsü. Modelde yol tek bir halkadır: kaynağın listesi. Gerçekte yol birkaç nesneden geçer ve her halka ayrı bir sahibe aittir; sızıntıyı kesmek için doğru halkayı seçmek, kaydı kaldırmaktan daha çok düşünce ister.

Bellek bütçesi bir alt sınır arayışı da değildir. Uygulama açıkken tutulan bir miktar nesne meşrudur: açık ekran, önbelleğe alınmış veri, kullanıcı oturumu. Sızıntı bütçesi bu meşru payı hesaba katar ve yalnız ekran kapandıktan sonra kalan fazlayı sınırlar. Bu yüzden ölçüm ekranın açık olduğu ana değil, kapandığı ana bakar ve model de sayacı her döngünün sonunda okur.

Sızıntı bütçesini sürümler arasında karşılaştırmak da yararlıdır. Önceki sürümde sekiz döngüde sıfır nesne tutan bir ekran, sonraki sürümde sekiz döngüde bir nesne tutuyorsa, bu tek nesne yeni bir sızıntının ilk işaretidir. Bütçe sıfır olduğunda bu işaret hemen aşım olarak görünür; bütçe geniş bırakıldığında yeni sızıntı fark edilmeden birkaç sürüm boyunca birikir.

## Yaygın Yanılgılar

**"Çöp toplama sızıntıyı temizler."** Çöp toplama yalnız erişilemeyen nesneleri serbest bırakır. Kaynak hâlâ ekrana bir referans tutuyorsa ekran erişilebilirdir ve toplanmaz. Sızıntı tam olarak, işi bitmiş bir nesneye hâlâ erişen bir yolun bulunmasıdır.

**"Zayıf referans her sızıntıyı çözer."** Zayıf referans, nesnenin toplanmasına engel olmaz; ama kaydı kaldırmanın yerini de tutmaz. Kaynak boşalmış kayıtları taşımaya devam edebilir ve bu birikim, nesne kadar büyük olmasa da gerçektir. Doğru çözüm, kaydı nesnenin yaşam döngüsüne bağlamaktır.

**"Ekran kapandıysa bellekten gitmiştir."** Kapanmak, ekranın kullanıcıya görünmemesi demektir; bellekten silinmesi demek değildir. Modelde kaydı kaldırılmayan ekran kapandıktan sonra da kaynağın listesinde kaldı ve sayaç bir daha düşmedi.

**"Düzeltme eski sızıntıyı geri alır."** Dersin sınırlayıcı ölçümü bunu açıkça gösterdi: düzeltmeden önce sızan dört ekran, düzeltmeden sonra da bellekte kaldı. Düzeltme yalnız yeni sızıntıyı durdurur.

**"Bütçe sızıntıyı tanımlar."** Bütçe sızıntıyı değil, bir kabul sınırını tanımlar. Aynı sızıntı dört bütçede dört farklı aşım verdi. Sızıntıyı yakalayan bütçe, uygulamanın hiç tutmaması gereken sayıya yakın bir değere konur.

## Sınırlayıcı Ölçüm

Sızıntıyı kökünden çözen düzen, dinleyiciyi ekranın yaşam döngüsüne bağlamaktır: ekran görünür olunca kaydolur, görünmez olunca kaydını kaldırır. Platformdaki biçimi şöyledir.

```kotlin
// Platform kodu: dinleyiciyi yasam dongusune baglama
import androidx.appcompat.app.AppCompatActivity

class OrdersActivity : AppCompatActivity() {
    private val listener = OrderListener { orders -> render(orders) }

    override fun onStart() {
        super.onStart()
        OrderSource.register(listener)
    }

    override fun onStop() {
        OrderSource.unregister(listener)
        super.onStop()
    }
}
```

Kayıt `onStart`ta yapılır ve `onStop`ta kaldırılır, böylece kaynağın ekrana giden yolu ekran görünmez olduğunda kesilir. Modelde bu, `yasam` biçimidir: ekran kaydolur ve kapanırken kaydını kaldırır. İki ölçüm yapıyoruz. Birincisinde düzen en baştan doğrudur. İkincisinde ilk dört döngü eski, sızdıran düzenle çalışır ve sonra düzeltme gelir.

```kotlin
// Main.kt — yasam dongusune baglama sonraki dongulerin tutulan nesnesini artirmaz
fun main() {
    val fixed = Source()
    val fromStart = (1..8).map { cycle(fixed, it, "yasam") }

    val patched = Source()
    val afterPatch = (1..8).map { cycle(patched, it, if (it <= 4) "guclu" else "yasam") }

    println("bastan: $fromStart")
    println("sonradan: $afterPatch")

    val overruns = listOf(
        Overrun("bastan", Budget("tutulan", 3), fromStart),
        Overrun("sonradan", Budget("tutulan", 3), afterPatch),
    )
    overruns.forEach { it.line() }
    countOverruns(overruns)
}
```

```text
bastan: [0, 0, 0, 0, 0, 0, 0, 0]
sonradan: [1, 2, 3, 4, 4, 4, 4, 4]
bastan: butce=tutulan<=3 | asan=0/8 -> icinde
sonradan: butce=tutulan<=3 | asan=5/8 -> cogunda
icinde: 1
azinda: 0
cogunda: 1
hepsinde: 0
```

En baştan doğru kurulan düzen **içinde**dir: hiçbir döngüde tutulan nesne yok. Sonradan yapılan düzeltme ise aynı bütçede **çoğunda** çıktı. Düzeltme ilk dörtten sonra yeni ekran sızdırmadı, sayı dörtte durdu; ama dördü de bellekte kaldı. Tutulan nesne sayısı sonraki döngülerde artmadı, önceki sızıntı da kendiliğinden gitmedi. Kuralın gözünde iki satırın farkı, bütçenin altında kalmak ile üstünde kalmak arasındadır; kuralın görmediği şey, ikinci satırda artışın durduğudur. Artışın durduğunu görmek için örneklerin kendisine, yani `sonradan:` satırına bakılır.

**BA3.** İkinci ölçümde ilk dört ekranın serbest kalmadığı, modelin kurduğu bir kuraldır. Gerçek uygulamada sızan ekran, onu tutan kaynak ortadan kalkana kadar, çoğu zaman süreç sonlanana kadar bellekte durur. Düzeltmeyi yayımlamak bu yüzden, düzeltmeden önce sızmış oturumları kurtarmaz. Bunlar ancak uygulama yeniden başlatıldığında temizlenir.

## Özet

- Bu dersin aşımı: çoğunda. Kaydı kaldırılmayan dinleyici her döngüde bir ekran tuttu ve sekiz döngünün beşi üç nesnelik bütçeyi aştı.
- Bellek sızıntısı, tutulan nesnelerin döngüler boyunca birikmesidir; tek bir döngüye bakarak görülmez.
- Aynı sızıntı sıfır nesnelik bütçede hepsinde, üçte çoğunda, altıda azında, sekizde içinde çıktı. Bütçe sızıntıdan geniş seçilirse sızıntı raporda görünmez.
- Zayıf referansla kaydolan ekran, modelde kapanınca tutulmadı. Gerçekte bu, çöp toplamanın ne zaman çalıştığına bağlıdır.
- Dinleyiciyi yaşam döngüsüne bağlamak sonraki döngülerde tutulan nesne sayısını artırmadı; ama önceden sızmış dört ekranı serbest bırakmadı.

## Sonraki Adım

Bellek, uygulamanın içinde tutulan şeydir. Uygulama aynı zamanda dışarıyla konuşur: ağa istek yollar, aygıtı uyandırır, pil harcar. Sıradaki ders, `Ağ ve Pil Profili`, bir eşitleme döngüsünde gönderilen istek sayısının bütçeyi kaç döngüde aştığını ve istekleri toplu göndermenin bu sayıyı nasıl düşürdüğünü ölçer.
