Android için Bellek Sızıntısı Nedir? (Android Memory Leak)

Hasan Kucuk
6 min readJul 10, 2019

--

Merhaba arkadaşlar bu yazıda uygulamalarımızda performans kaybına sebep olan bellek sızıntılarından bahsedeceğim. Pek çok kere uygulamanın anlamsız yere çöktüğü ve sebebini çözümleyemediğimiz durumlarda bunun müsebbibi bir bellek sızıntısı olabilir. Ben araştırıp deneyimlerken keyif aldım. Umarım faydalı bir yazı olmuştur. O halde buna birlikte bir göz atalım.;)

Bellek taşmasına değinmeden önce belleğin ne olduğuna bir bakalım. Temelde bellek (RAM — Random Access Memory) uygulamamız için kodladığımız değer tipleri ve nesnelerin uygulama çalıştığı esnada tutulduğu yerdir.

Bellek kendi içerisinde Heap & Stack adlı iki mantıksal bölümden oluşur.

Stack

Stack bellek içerisinde değer tipleri ve nesne referanslarını tutulur. Belli bir hiyerarşi söz konusudur. Son giren ilk çıkar (Last-in-First-out) prensibine göre çalışır. Bir nesne oluşturduğumuzda nesne Heap üzerinde yer tutarken nesnenin referansı Stack üzerindedir. Stack kısa ömürlüdür. Yani metodun son scope’una geldiğinizde metoda bağlı değer tipleri ve nesne referansları Stack üzerinden temizlenir. Stack bellek üzerinde bir taşma yaşanırsa “StackOverFlowError” hatası oluşur.

Heap

Heap ise dinamik bir bellek alanıdır. Belli bir hiyerarşi olmadığından Stack’e göre daha yavaştır. Bir nesne oluşturduğunuzda her zaman Heap üzerinde depolanır. Stack’den farklı olarak metodun son scope’una geldiğinizde bellek temizlenmez. Belleğin temizlenmesi JVM tabanında Garbage Collection’un referansı bulunan nesneleri işaretlemesi ve bunun haricinde bulunanları temizlemesiyle gerçekleşir. Heap üzerinde bir taşma yaşanırsa “OutOfMemoryError” hatası oluşur.

Peki gerçek dünyada nasıl çalışıyor?

Hadi Stack & Heap yapısını ufak bir kod parçası üzerinden inceleyelim.

Aşağıdaki şekil yukarıdaki kod bloğunun Stack & Heap üzerindeki yapısını temsil etmektedir.

//1. satırda onCreate() metodu için kullanılacak bellek stack’de oluşturulur.

//2. satırda onCreate() metodunun stack bellekteki yerinde bird referansı oluşturulurken bunun işaret ettiği Bird nesnesi de heap üzerinde tutulur.

//3. satırda int tipinde bir primitive değişken stack üzerinde oluşturulur.

//4. satırda stack içerisinde birdsFly() metodu için bir alan tahsis edilir.

//5. ve 6. satırlarda 2. ve 4. satırlarda gerçekleşen olayların bir benzeri yaşanır.

//7. ve 9. satırlarda metodların parametrelerinde yer alan nesneler için referanslar stack üzerinde oluşturulurken, bu referansların işaret ettiği nesneler heap üzerinde oluşturulur.

//8. satırda metod sonlanır. Dolayısıyla stack üzerinde tuttuğu bellek serbest kalır.

Metodlar tamamlandığında ne olur?

birdsFly() ve elephatsRun() metodlaı görevden tamanlandığında tuttuğu referanslar stack üzerinden silinir.

onCreate() metodu da tamamlandığında uygulama üzerinde bulunan tüm referanslar stack üzerinden temizlenir. Fakat bu durum Heap için geçerli değildir. Heap belleğin temizlenmesi Garbage Collection’ a bağlıdır.

Her uygulama görevlerini yerine getirebilmek için belleğe ihtiyaç duyar. Uygulama tarafından ihtiyaç duyulan belleğin sağlanamaması durumunda uygulama görevlerini yerine getiremeyecek ve ANR(Application Not Responding) uyarısı verecektir. Android Sistemi bunun önüne geçebilmek için bellek azaldığında JVM üzerinde Garbage Collection’u (Çöp Toplayıcısı) tetikler. Böylece bellekte artık kullanılmayan verileri temizleyerek hafızayı geri kazanmayı amaçlar. Bunu basit olarak 3 adımda tamamlar;

  • Aktif olan nesnelerin bellekteki referanslarını tespit eder ve işaretler.
  • İşaretlenmemiş tüm nesneleri bellekten temizler.
  • Aktif nesneleri yeniden düzenler.
Garbage Collection Step 1

Garbage Collection uygulamamızın bulunduğu kökten başlayarak nesneleri işaretler. Bir nesneye kökten başlayarak bellekteki referansına kadar ulaşılamıyorsa, uygulamanın o nesneye erişebilmesinin hiçbir yolu yoktur.

Garbage Collection Step 2

Örneğin belleğimizde böyle bir yığın olduğunu düşünelim. Böyle bir durumda kökten itibaren bağı hiç kopmamış olan nesneler canlı durumda, erişebileceğimiz nesnelerdir. Garbage Collection bu nesneleri tek tek işaretleyecektir.

Garbage Collection Step 3

Garbage Collection tarafından işaretlenen nesneler(yeşil kutular ile temsil edilenler) bellekte aktif olarak görev yapan nesnelerdir. Bunlar dışında kalanlar ise ulaşılamayan yani bellekten temizlenmesi gereken nesnelerdir. GC’ bu nesneleri bellekten temizleyerek bellek yönetimini sağlar.

Peki Bellek Sızıntısı Nedir?

Temelde bellek sızıntısı, bir bellek tahsis edildiğinde fakat hiçbir zaman serbest bırakılamadığında ortaya çıkar. Bu Garbage Collection’un sızıntı yaşayan nesneleri bellekten toplayamaması anlamına gelir. Başlangıçta bu hissedilir bir sorun değildir. Hatta QA testlerinde bile fark edilmeyebilir. Fakat kullanıcı uygulamayı kullandıkça sonlanmamış nesne sayısı artacak ve bir süre sonra uygulama OutOfMemoryError verecektir.

Kullanılmayan nesnelere erişilebilir nesnelerden bir şekilde başvurulduğu durumda, Garbage Collection kullanılmayan nesneleri yararlı nesne olarak işaretleyecektir. Bu nedenle yararlı nesne olarak işaretlenen nesneler bellekte kalmaya devam edecektir. Bu durum bellek sızıntısını oluşturur.

Bellek Sızıntısı Neden Kötü?

Bir bellek sızıntısı oluştuğunda bellek yoğun bir şekilde kullanılmayan nesnelerle dolu olarak kalacaktır. Ve yeni bir işlem gerçekleştirmeye izin vermeyecektir. Böylece Garbage Collection belleği temizlemek için sürekli devreye girecektir. Garbage Collection devreye girdiğinde arayüzün işlenmesi bir miktar gecikir. Android pencere hızı 16ms olduğundan bu gecikmeler insan gözünün seçemeyeceği kadar hızlıdır. Bunu bir takılma olarak göremeyiz. Fakat Garbage Collection 16ms’den daha uzun sürdüğünde Android kareleri kaybetmeye başlar. Genel olarak insan gözü 100ms-200ms aralığında yavaşlıkları seçmeye başlar. Bu gecikme devam ettiğinde Activity Manager ve Window Manager tarafından sürekli izlenen sistem; bir tuşa basıldığında ya da ekrana dokunulduğunda cevap vermemeye başlayacaktır. Bu süre 5 saniyeden uzun olduğunda Android sistemi Application Not Responding(ANR) bildiren bir popup ile kullanıcıyı uyarır.

1- Application Not Responding(ANR)

2- Uygulama yanıt vermez

3- Uygulama çöker (OutOfMemoryError)

Bellek Sızıntısına Neden Olan Yaygın Hatalar;

1. Kaydı Sonlandırılmamış Dinleyiciler (Unregistered Listener)

Uygulama geliştirme süreçlerinde bir çok özelliği gerçekleyebilmek için Listener(dinleyici) ve Broadcast Receiver(alıcı)ların özelliklerinden yararlanıyoruz. Fakat bu listener ve receiver’larla işimiz bittiğinde ya da Activity- Fragmentlerimiz sonlandığında kayıtlarını sonlandırmayı (unregister) unutmamalıyız. Bu basit hata büyük bir bellek sızıntısına neden olabilir.

Unregistered Listener

Örneğin sensör dinleyicisine kayıt olduğumuz bu örnekte; registerListener() sınıfımızı çağırdıktan sonra bağlı bulunduğu Activity sınıfımızı sonlandırdığımızda dinleyici çalışmaya devam edecektir. Bu da aşağıda LeakCanary’de görüntülendiği gibi bir bellek taşmasına sebep olacaktır.

Basit bir önlemle bunun önüne geçebiliriz. Bu tarz bellek sızıntıları başta pek hissedilmeyecek türden de olsa zamanla uygulamamızın sağlığını tehlikeye sokacaktır.

Bunun önüne geçmek için onStop() ya da onDestroy() anında kullandığımız dinleyici(listener) ya da alıcı(receiver) kaydını sonlandırmalıyız.

2. Statik Activity ya da View Referansları

Nesne ya da view’leri statik olarak tanımlamaktan kaçınmalıyız. Eğer bir nesne ya da view’i statik olarak tanımlarsanız Activity ya da Fragment sonlandığında dahi GC’ bunları toplayamaz.

Yine de tanımlamamız gerekiyorsa WeakReference olarak tanımlamalıyız. Böylece GC’ devreye girdiğinde zayıf olarak işaretlenen referanslar bellekten temizlenecektir.

3. İç İçe Sınıf Referansları (Inner Class Reference)

İç içe sınıfları bir başka model sınıfında ihtiyaç duymayacağımız data model alt sınıflarında ya da AsyncTask gibi işlemlerde sıklıkla kullanırız. Fakat aşiağıda ki örnekte olduğu gibi bir iç içe sınıf çağırdığımızda AsyncTask işlemi Activity çalışma zamanından uzun sürdüğünda ya da yatay ekrana geçtiğimizde bellek taşmasına sebep olacaktır. Bu problemi önlemek için Java’da class’ımızı static olarak tanımlayabiliriz. Kotlin’de ise “inner” olarak belirtmezsek sorunu çözmüş oluruz.

4. Anonim Sınıflar (Anonymous Classes)

Anonim sınıf oluşturmak kod kirliliğini önlemek açısından kullandığımız bir yol olsa da doğru şekilde kullanmazsak başımıza bela olabilir. Aslında anonim sınıflar statik olmayan iç içe sınıflardan başka bir şey değildir. Bu yüzden bu classları statik classlara çevirerek bellek taşmasından kaçınabiliriz.

Bu şekilde bellek taşması ihtimali olan pek çok durumda Android Studio bizi zaten uyarıyor. Bunları dikkate aldığımızda uygulamalarımız daha performanslı olacaktır.

Kısaca

  • View, Activity ya da Context kullanırken statik olarak kullanmaktan kaçınmalıyız.
  • Activity içindeki bir class’a referans vermemeliyiz.
  • BroadcasReceiver ya da Listener kullandığımızda Activity sonlanırken kayıtlarını sonlandırmalıyız.
  • Activity ya da View’ları statik olarak kullanmamız gerekecekse zayıf referans olarak(WeakReference) tanımlamalıyız.

--

--