Seriye Memory’le devam ediyorum. Rendering ve Compute yazılarına ilgili linklerden erişebilirsiniz.

Hardware’e yakın olarak bilinen programlama dillerinde, C, C++, Fortran gibi, genellikle yazılımcılar bellek yönetimini kendileri yaparlar. Allocating memory ve iş tamamlandığında de-allocating tamamen yazılımcılara aittir. Böyle bir durumda, tüm bellek yönetimi size kalmış olduğu için ne kadar doğru kullanıldığı da aslında tamamen yazılımcının bilgisi ve becerisine bırakılmış olur. Kabul edelim ki bu gerçekten de zordur ve çoğu yazılımcı bu gibi durumlarda hatalı kod yazabilir. Bu da memory leak’lerin oluşmasına sebep olur. Bu kaostan kurulmak için ise, managed memory dilleri icat edildi.

Bu diller runtime’da memory allocation’larını track eder ve uygulamanın kendisi tarafından uzun süre kullanılmadığını fark ettiği anda da memory’yi release eder. Tüm bunlar yazılımcının hiç bir müdahalesi olmadan gerçekleşir. Bu aslında hepimizin bildiği garbage collection‘dır.

Garbage Collection’ın temel prensipleri:

  1. İlerde erişilmeyecek olan data objesini bul. (Kod tarafından artık kullanılmayacak olan memory)
  2. Bu objeler tarafından kullanılan objeleri tasfiye et.

Diğer taraftan garbage collection, tehlikeli de olabilir. Açıklamak gerekirse eğer kodunuzda 20,000 allocation varsa, hangilerine artık ihtiyaç olmayacak? Ya da garbage collection event’ini artık kullanılmayan memory’yi kazanmak için ne zaman çalıştırmalıyız?

Screen Shot 2015-11-15 at 23.47.44

Android runtime’ında ise memory heap’ler allocation’ın tipine ve ilerideki GC eventlerini sistemin en iyi nasıl organize edebileceğine bağlı olarak boşluklara segment’lenir. Yeni bir obje allocate edildiğinde, hangi android runtime’ını kullandığınıza bağlı olarak, bu karakteristikler de göz önüne alınarak, en uygun yere yerleştirilir. Screen Shot 2015-11-15 at 23.53.44

Burada önemli nokta, her bir space belli bir size’a sahiptir. Objeler allocate edildiğinde, ortak size’a bakarız ve space dolmaya başladığında, system ilerideki allocation’lara yer ayırabilmek için GC event’i işletme ihtiyacı duyacaktır.  GC eventler hangi Android runtime kullandığınıza bağlı olaScreen Shot 2015-11-15 at 23.58.36rak farklı çalışmaktadır. Mesela Dalvik’de, işlem tamamlanana kadar çalışan tüm kod duracaktır. Problematik olduğu durum ise bu GC’lerin normalden uzun sürdüğü ya da tonlarcasının bir kerede olduğu zamanlardır ki bu da frame time’ınızı yiyeceği anlamına gelmektedir.

Her ne kadar GC eventler kendi kendilerine handle ediliyor olsalar da yine de performans problemleri oluşabilir. Bunun sebeplerinden ilki, bir frame’de GC’ye çok fazla diğer işlere daha az zaman ayırmaktır. Bu durumda GC’den arta kalan zamanlarda diğer işler yapılacaktır ki bu da 16 ms’lık frame time’ı aşmanıza neden olabilir.  İkinci olarak, kodunuzda bazı parçalar GC’nin daha sık tetiklenmesine ya da normalden uzun sürmesine sebep olabilir. Mesela uzunca bir süre devam edecek bir loopda, çok sayıda ve gereksiz obje tanımı yapıyorsak, memory heap’ini çok sayıda objeyle dolduruyor ve GC’yi çok fazla çağırıyor oluruz.

Her ne kadar managed memory environmentı kullansak da memory leak’ler yine de oluşabilir. Android SDK’daki Memory Monitor bu noktada imdadımıza yetişiyor.

Memory Monitor

Memory Monitor uygulamanızın memory’yi nasıl kullandığını gösteren bir tool’dur.

  • Kullanılan ve kullanılabilir olan momory’yi grafik üzerinde ve garbage collection event’lerini zamana bağlı gösterir.
  • Bir uygulamanın devamlı yavaş çalışması, garbage collection event’lerinin fazla sayıda kullanılmasından kolayca test edilebilir.
  • Bir uygulama memory’nin yeterli olmamasından dolayı crash ediyorsa kolayca test edilebilir.

gettingstarted_image001Uygulamanızı emulatorde yada cihazda çalıştırdıktan sonra Memory Monitor’ü Tools->Android->Memory Monitor’u izleyerek ya da Android tab’ının altında açabilirsiniz.

Memory Monitor cihazınızı track etmeye başladığında, memory’yi zamana bağla track eden bir stacked grafik belirir. Alttaki resimde görebilirsiniz.

Koyu Mavi: Uygulamanızın o an için kullandığı bellek miktarı.

Açık Mavi: Kullanılabilir, henüz allocate edilmemiş memory

gettingstarted_image003

Zaman aktıkça, grafik de kendini update eder ve memory kullanımındaki değişiklikleri gösterir.

Uygulamanız memory allocate ettiğinde ve bıraktığında grafiğin görünümü değişir. Allocate edilmiş momory’de büyük bir düşüş gördüğünüzde ise bu GC eventinin çalıştığı anlamına gelmektedir. Aynı şekilde ani bir yükseliş görürseniz bu da uygulamanızın memory’ye ihtiyacı olduğu için allocation yapıldığı anlamına gelir. Aksi takdirde uygulamanız crash ederdi.

Aşağıdaki resimdeki gibi, ilgili butona tıklayarak, GC eventini çalışmaya zorlayabilirsiniz.

gettingstarted_image005

Yukarıdaki örnekte garbage collection’ın çalışma şekli gayet sağlıklıdır. Belli aralıklarda memory allocationlar ve de-allocationlar yapılmış. Alttaki resim ise problematik bir çıktıyı örnek göstermektedir. Sizin de gördüğünüz gibi, uygulamada çok kısa sürede çok fazla allocation yapmış ve hemen ardından da bırakılmış. Bu da aslında çok sayıda GC eventi anlamına gelmektedir. Yani GC eventlerine çok rendering’e streaming’e az zaman ayrılmıştır.

Screen Shot 2015-11-16 at 21.37.43

Peki oluşan memory leak’leri nasıl tespit ederiz. Bu konuda da yine Android SDK’daki Heap Viewer bize yardımcı olacak.

Heap Viewer:

Heap Viewer, uygulamanızın hangi tip objeleri allocate ettiğini, ne kadar ne boyutta heap kullandığını gerçek zamanlı olarak raporlayan bir tool’dur.

Android 5.0 ve üzerinde kullanabilirsiniz.

gettingstarted_image01

Tools->Android->Android Device Monitor üzerinden tool’a erişebilirsiniz.

gettingstarted_image02

gettingstarted_image03

Bir heap dump kaydetmek için, üstteki resimdeki işaretli, Dump Java Heap ikonuna tıklayın. Captures tabı altında Snapshot-yyyy.mm.dd-hh.mm.ss.hprof adıyla bir heap snapshot belirecektir.

Heap update’leri her bir GC eventinden sonra oluşacaktır. Initiate GC ikonuna tıklayacak bir garbage collection event’ini tetikleyebilirsiniz.

Heap snapshot dosyasını çift tıklayarak, heap viewer’ı açabilir ve heap’deki anlık allocation’lara detaylı olarak görebilirsiniz. Daha detaylı bilgi almak için ise data tiplerinden birine tıklayabilirsiniz. Mesela aşağıda bytearray(object[],int[],float[])’a tıklandığında, alt kısımda kırmızı tonlarında görünen allocationların sayısını gösteren histogram ve aynı zamanda bu data tipi için spesifik memory size belirmiş olacak. Her bir data tipinin karşısında, adeti ne kadar size kapladığı yazmaktadır ki bu yardımcı bir bilgidir.

gettingstarted_image05

Memory leak’lerden konuşmaya devam edersek, Memory Leak’ler çok sinsidirler. =) O kadar yavaş ve gizlidirler ki bazen memory leak’inizin olduğunu anlamanız günler hatta haftalar alabilir. Bir diğer taraftan da aslında kullanıcılarınız uygulamanızdaki gizemli yavaşlıktan şikayet etmeye başladığında gerçekte memory leak’leriniz olduğunu farkedersiniz. Bundan kaçınmak için, biraz sabır(:)) ve doğru tool’larla bu leak’leri uygulamanızdan çıkarabilirsiniz.

Aslında yapılması gereken basit. Uygulamamızda bir sorun olup olmadığını anlamak için Memory Viewer’la durumu gözlemleriz. Eğer bir sorun görürsek Heap Viewer’la daha detaylı inceleriz.

Best Practises(Memory Leak’lerden nasıl kaçınırız):

  • Kodunuzdaki objelerin yaşamdöngüsünü inceleyin ve ihtiyacınız olmadığını referansları temizleyin.
private void init() {
        ListenerCollector collector = new ListenerCollector();
        collector.setListener(this, mListener);
    }

Mesela yukarıdaki kod parçasında, custom view’in init’inde activity için tüm listenerlar store ediliyor. Fakat, eğer listener’ı destroy anında temizlemezseniz bir slow leak yaratmış olursunuz.

Örneğin orientation değişikliğinden dolayı activity’niz recreate edildiğinde, view’le ilişik bir listener da yaratılır. Ancak activity destroy edildiğinde bu listener release edilmez. Bu da garbage collector tarafınfan hiç bir listener’ın tasnif edilmeyeceği memory leak oluşturacağı anlamına gelir.

Device’ın orientation’ı değişip activity’nin onStop() methodu tetiklendiğinde, listenerların temizlendiğinden emin olmalısınız.

Bir diğer faydalı tool da Allocation Tracker’dır.

Allocation Tracker:

Allocation Tracker, uygulamanızın memory allocationlarını kaydeder ve stack çağrılarıyla profiling cycle, boyut ve allocating kodu için allocate edilmiş objeleri listeler.

Ne için faydalıdır?

  • Benzer obje tipleri kısa bir zaman dilimi için nerede allocate deallocate edildi ?
  • Kodunuzda verimsiz memory kullanımına sebep olan yerlerin bulunması

Allocation Tracker’ı kullanmadan önce, Memory Monitor Tool’la kodunuzu profile etmelisiniz. Ardından eğer kısa süre içerisinde çok sayıda garbage collection event’i görüyorsanız, Heap Viewer’ı kullanarak bu duruma sebep olabilecek, obje tiplerini tanımlayabilirsiniz ve son olarak, Allocation Tracker’la bu kodunuzda nerede yer alıyor, anlayabilirsiniz.

Allocation Tracker, uygulamanızın çalışmaya devam ettiği profiling cycle’ı boyunca her bir  her bir memory allocation’ı kayıt altına alır. Allocation Tracker’a başlamasını söylediğimiz gibi loglamayı bırakmasını da biz söyleriz.

gettingstarted_image01

  1. Android Studio’nun altındaki Android butonuna tıklayın.
  2. Android Device Monitor kullanıyorsanız, RECORD butonuna tıklayın. Eğer Android Studio kullanıyorsanız da Memory Monitor ikonuna tıklayın.
  3. Uygulamanızla etkileşime geçin.
  4. Allocation Tracking işleminin sonlanması için Stop button’una tıklayın.gettingstarted_image03
  5. Birkaç sn sonra, sizin kaydedilmiş datanızla bir panel açılır. (Allocation’lar cihaza loglanır, data file bilgisayara transfer edlir, bilgi parse edilir ve görüntülenir.)
  6. Panel tablo olarak karşımıza çıkar.
    • Her bir satır, bir memory allocation eventini temsil eder.
    • Her bir sütun, allocationla ilgili bilgiyi temsil eder. (obje tipi, thread, size)
    • Sütunlar yer değiştirilebilir, yeniden boyutlandırılabilir ve sort edilebilir şekildedir.
  7. Bir objenin full stack trace ‘ini görmek için ise tıklamanız yeterli.

Çok kısa zamanda çok fazla obje allocate edilmesi, memory churn. Memory leak’lerden kaçınmak için temporary objelerinizi loopların ya da resursive çalışan methodların içinde tanımlamamalısınız.

 

Referanslar: