OutOfMemoryError, Java Sanal Makinesi (JVM) veya Android Runtime (ART) yığında (Heap) yeterli alan olmaması nedeniyle yeni bir nesne için bellek ayıramadığında oluşan ölümcül bir istisnadır. Square Engineering'e göre, mobil uygulamalardaki OutOfMemoryError'ların %70'i gerçek sınır aşımından değil, bellek sızıntılarından kaynaklanır. OOM'un nedenlerini anlamak, uygulama kararlılığının anahtarıdır.
Anahtar Noktalar
OutOfMemoryError (OOM), Java/Kotlin'de VirtualMachineError ailesinden, yeni bir nesne için bellek ayıramama durumunu bildiren bir istisnadır. Kontrol edilen istisnaların aksine, OOM bir Error'dür ve catch ile işlenmesi gerekmez — ancak teknik olarak yakalanabilir. OOM oluştuktan sonra, uygulama genellikle kararsız bir durumdadır ve sonlandırılması önerilir.
Android'de, her uygulamanın cihaz üreticisi tarafından belirlenen bir Heap sınırı vardır. 6+ GB RAM'e sahip modern akıllı telefonlar için sınır 256–512 MB, bütçe cihazları için 128–192 MB'dir. Tüm canlı nesnelerin toplam hacmi bu sınırı aştığında, ART OutOfMemoryError fırlatır.
Anlamak önemlidir: OOM her zaman cihazın fiziksel belleğinin tükendiği anlamına gelmez. Uygulamanın sistem tarafından belirlenen Heap sınırını tükettiği anlamına gelir. Diğer uygulamalar boş belleğe sahip olabilir, ancak Android'deki süreç izolasyonu nedeniyle uygulamanız bunu kullanamaz.
Beş senaryo düzenli olarak mobil uygulamalarda OOM'a yol açar. Her senaryo belirli bir veri türü veya işlemle ilişkilidir.
Bitmap, Android uygulamalarındaki ana bellek tüketicisidir. FullHD görüntüyü (1920 × 1080) orijinal boyutta yüklemek, ARGB_8888 formatında 8,3 MB yer kaplar. Bir RecyclerView'da bu tür 50 görüntü varsa, bu 415 MB'dir ve herhangi bir cihazın Heap'ini aşar. inSampleSize olmadan görüntü yüklemek, zayıf cihazlarda garantili OOM'dur.
Otomatik ölçeklendirme için Glide veya Coil kullanın. Bu kütüphaneler, görüntüleri orijinal çözünürlük yerine View'a uygun boyutta yükler. BitmapFactory.Options'ın doğrudan kullanımı için inSampleSize uygulayın: bunu ikinin katı olarak hesaplayın, böylece nihai boyut 2048 × 2048 pikseli aşmaz. Ayrıca, şeffaflığı olmayan görüntüler için ARGB_8888 yerine RGB_565 kullanın — bu, bellek tüketimini yarıya indirir.
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
val opts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, opts)
opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
opts.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, opts)
}
Tek bir birkaç KB'lık sızıntı OOM'a neden olmaz. Ancak her ekranda düzinelerce sızıntı birikir: her ekran geçişi bir sızıntı ekler, GC nesneleri serbest bırakamaz ve Heap dolar. Tipik bir desen: kullanıcı profil ekranını 20 kez açar ve kapatır → Heap 200 MB büyür → uygulama OOM ile çöker.
Otomatik sızıntı tespiti için projeye LeakCanary kurun. Tam yığın izi ile sızdırılan her nesneyi gösterecektir. Tüm sızıntılar düzeltildikten sonra Heap tüketimi sabit hale gelir: bir ekran kapatıldıktan sonra bellek temel seviyeye döner.
Tüm dosyaları byte[] içine yüklemek OOM'a giden doğrudan bir yoldur. 50 MB'lık bir JSON dosyası, ayrıştırma sırasında aynı boyutta bir dize artı bir DOM modeli oluşturacaktır. Belleğe yüklenen video dosyaları, ses tamponları ve büyük protobuf veri kümeleri — tümü tek bir işlemde Heap sınırını aşabilir.
Büyük verileri akışlar kullanarak işleyin: 4–8 KB arabellekli InputStream, akışlı JSON ayrıştırıcı (Jackson veya JsonReader ile Gson), video için MediaCodec. Kullanılabilir Heap'in %10'undan büyük dosyalarda asla File.readBytes() çağırmayın.
Bir döngüde arada GC olmadan yoğun nesne oluşturma, özellikle küçük Heap'li cihazlarda OOM'a yol açabilir. Örnek: GC'nin toplayamadığı 100.000 nesneyi for-döngüsünde oluşturmak. Bu, oyunlarda ve grafik düzenleyicilerde daha yaygındır.
Toplu olarak oluşturulup yok edilen nesneler için Object Pool kullanın. Sayısal veriler için ilkel türler kullanın (List<Float> yerine FloatArray). ViewHolder Pool ile RecyclerView, UI bileşenleri için bu sorunu çözer.
Parçalanma, toplamda yeterli boş bellek olmasına rağmen yeni bir nesne için bitişik blok bulunmaması durumudur. ART, GC sırasında Heap'i sıkıştırır, ancak her zaman başarılı olmaz. Büyük diziler (Bitmap, byte[]) parçalanmaya karşı en hassas olanlardır.
Android 8+'da ART, genç ve yaşlı nesneleri ayırarak parçalanmayı azaltan Nesil GC kullanır. Yine de, aynı havuzda farklı boyutlarda parçalar ayırmaktan kaçının — önceden ayrılmış sabit boyutlu arabellekler kullanmaya çalışın.
Android'de Heap sınırı bir sabit değildir — üreticiye, cihaz modeline ve işletim sistemi sürümüne bağlıdır. Google, Uyumluluk Tanım Belgesi (CDD) aracılığıyla minimum gereksinimleri belirler, ancak üreticiler gerçek değerleri belirler.
| Cihaz Kategorisi | Tipik Heap | largeHeap |
|---|---|---|
| Bütçe (1–2 GB RAM) | 128–192 MB | 256–384 MB |
| Orta Sınıf (3–4 GB RAM) | 256–384 MB | 512 MB |
| Amiral Gemisi (6+ GB RAM) | 384–512 MB | 768 MB–1 GB |
| Tabletler (4+ GB RAM) | 256–512 MB | 768 MB |
| Wear OS | 32–64 MB | Yok |
Bildirimde android:largeHeap="true" aracılığıyla artırılmış bir sınır talep edebilirsiniz. Dikkatli kullanın: Heap'i artırmak sızıntı sorununu çözmez ve sistem sizin uygulamanız için bellek boşaltmak amacıyla diğer uygulamaları öldürmek zorunda kalırsa kullanıcı deneyimini kötüleştirebilir. Wear OS için Heap sınırı minimumdur — yalnızca 32–64 MB, largeHeap burada kullanılamaz ve bellek tasarrufu iki kat önemlidir.
OOM teşhisi, bir Heap Dump analizini ve hangi nesnelerin bellek tükettiğini anlamayı gerektirir. Android Studio gerekli tüm araçları sağlar.
Adım 1: OOM anını yakalayın. Android Memory Profiler'da Record memory allocations'a tıklayın ve çökmeye neden olan senaryoyu çalıştırın. Profiler, OOM'dan önce bellek ayırmalarında bir artış gösterecektir. OOM tekrarlanabilir değilse, hata ayıklama yapısında android:smallHeap ile Heap'i azaltın veya manuel GC çağrısıyla DDMS kullanın.
Adım 2: En yoğun yükte (OOM'dan önce) bir Heap Dump alın. Android Studio'da Dump'ı açın: Classes sekmesi Retained Size'a göre sıralanmıştır. En büyük nesneler Bitmap, byte[], String'dir. Her Bitmap için boyutu (genişlik × yükseklik × 4 bayt) ve Stack Trace aracılığıyla yükleme yolunu kontrol edin.
Adım 3: Yinelenen nesnelerin sayısını analiz edin. 200 özdeş Fragment veya Activity görüyorsanız — bu bir sızıntıdır. Aynı boyutta 500 Bitmap varsa — bu bir görüntü önbellekleme sorunudur. MAT (Memory Analyzer Tool), Heap'in %80'ini hangi nesnelerin tuttuğunu gösteren bir Dominator Tree ile daha derin analiz sağlar.
// adb aracılığıyla Heap Dump komutu
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.
Kapsamlı bir OOM önleme stratejisi, beş koruma seviyesi içerir: mimari kararlardan üretim izlemesine kadar.
ViewModel + Repository deseni, verileri UI'dan ayırır ve ekran döndürmede View'in tutulmasını önler. ViewModel, Activity'den daha uzun yaşar, verileri kaybolmaz ve View, bellekte verileri çoğaltmadan yeniden oluşturulabilir. Açık durum yönetimi için LiveData yerine StateFlow kullanın.
Glide, görüntülerle çalışmak için zorunlu bir kütüphanedir. Otomatik olarak ölçekler, önbelleğe alır (disk + bellek) ve Bitmap'i geri dönüştürür. Büyük listeler için diskCacheStrategy ve skipMemoryCache'i yapılandırın. Animasyonlu görüntüler için Glide'ı GIF/WebP ile kullanın — Bitmap dizisinden daha az bellek kaplarlar.
Firebase Performance Monitoring, bellek tüketimini gerçek zamanlı olarak izler. Heap kullanımı sınırın %80'ini aştığında bir uyarı ayarlayın — bu bir inceleme sinyalidir. Crashlytics, OOM'u bir istisna olarak toplar ve çökmeden önceki son bilinen Heap durumunu gösterir. Android 11+ için, OOM sonlanmalarını tespit etmek üzere ApplicationExitInfo kullanın.
Uygulamayı minimum Heap'li (128–192 MB) cihazlarda test ettiğinizden emin olun. Küçük ekranlı ve küçük Heap'li bir emülatör, bütçe cihazını simüle eder. Uygulama böyle bir cihazda çalışıyorsa, amiral gemilerinde OOM sorunu olmayacaktır. Farklı fiyat kategorilerindeki gerçek cihazlarla Firebase Test Lab kullanın.
// Ağır işlem öncesi kullanılabilir Heap kontrolü
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // %50 tampon
}
Sıkça Sorulan Sorular
Teknik olarak evet, ancak önerilmez. OOM'dan sonra uygulama kararsız bir durumdadır: yeni ayırmalar başarısız olabilir ve bazı nesneler kısmen oluşturulmuş olabilir. Catch'teki tek makul eylem, günlüğe kaydetme ve Activity'yi yeniden başlatmadır.
Heap sınırı cihazlar arasında farklılık gösterir. 300 MB gerektiren bir işlem, 192 MB sınırı olan bir cihazda çöker ancak 512 MB ile amiral gemisinde başarılı olur. OOM senaryolarını tespit etmek için minimum özelliklere sahip cihazlarda test yapın.
largeHeap sınırı artırır ancak uygulamayı hızlandırmaz. GC duraklamaları uzar çünkü büyük bir Heap'i toplamak daha fazla zaman alır. Sistem, bellek sağlamak için arka plan uygulamalarını öldürebilir. largeHeap'i yalnızca nesnel olarak çok fazla belleğe ihtiyaç duyan uygulamalar için kullanın (kameralar, düzenleyiciler).
OOM, Heap yetersiz olduğunda uygulama içindeki bir istisnadır. Sistem öldürmesi (Low Memory Killer), diğer uygulamalar için bellek boşaltmak amacıyla bir işlemi öldürmek için Linux çekirdeğinin kararıdır. Sistem öldürmesinde, uygulama bir istisna almaz — işlem basitçe sona erer.
Formül: genişlik × yükseklik × bytesPerPixel. ARGB_8888 = 4 B/piksel, RGB_565 = 2 B/piksel. ARGB_8888'de bir FullHD Bitmap (1920 × 1080) = 8,3 MB. 4K Bitmap (3840 × 2160) = 33 MB. Görüntüleri her zaman ekranda görüntüleme için gerekli boyuta ölçeklendirin.
Özet
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.
Ayrıca okuyun