post-images/eliminate-obsolete-object-references/cover.jpg

Kullanılmayan Nesne Referanslarının Kaldırılması

Bugün kaldığımız yerden devam edip Effective Java kitabının ilk ünitesi olan Creating and Destroying Objects'in 7. maddesini ele alacağız.

Bugün ki konumuza Garbage Collection üzerinde gireceğim. Java geliştiricilerinin diğer diller gibi bellek yönetimiyle uğraşmazlar bu işlemi garbage collection üstlenir ve gerçekten bu konuda gayet başarılı. Fakat doğrudan "Her şeyi garbage collection yapıyor benim belleği düşünmeme gerek yok" algısı da pek gerçekci değil. Ne kadar bizim için belleği yönetsede kullanılmayan obje referanslarının temizlenmemesi (serbest bırakılmaması) konusunda memory leak'lere (bellek sızıntısı) neden olabiliyor. Seninle nasıl memory leak olmadan objelerimizi bellekten sileriz konusu uzerine yoğunlaşacağız.

Tabii ki en güzel yol örnek üzerinden gitmek, örneğimiz:

// Can you spot the "memory leak"?  
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        return elements[--size];
    }

    /**  
     * Ensure space for at least one more element, roughly * <p>  
     * doubling the capacity each time the array needs to grow.  
     */
    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

Kodumuza alıcı gözüyle baktığımızda herhangi bir sorun gözükmemekte. Hatta tüm testlerimiz başarılı bir şekilde sonuçlanacaktır. İşin kötü yanlarından biri de bu. Ne kadar her şey pespembe görünse de aslında bu kod içerisinde memory leak mevcut. Memory leak neye sebep olabilir konusuna geldiğimizde olağanüstü durumlarda bu tür memory leak'ler disk paging (disk sayfalamaya) ve hatta OutOfMemoryError ile uygulamanızın çökmesine sebep olabilir, ancak bu tür hatalar nispeten nadirdir.

Yukarıdaki kod örneğinde yer alan stack yapısını yorumlamak gerekirse: Bir nesnenin boyutu artırıldığında ve daha sonrasında azaltıldığında, azaltılan miktarın normal şartlarda bellekten kaldırılması gerekir. Bu işlemi garbage collector yapmaz daha doğru söylem ile yapamaz. Çünkü ne kadar sizler stack içerisinden çıkarılan (pop()) elemanlara tekrar erişemesenizde aslında arka planda stack sizler için bu nesnelerin referanslarını tutmaktadır. Hatta daha kötüsü burada zincirleme reaksiyonda gerçekleşebilir. Stack içerisine eklenen diğer nesneler içinde de başka nesneler varsa bu nedenle onlarda bellekten silinmez. Garbage collector bellekte bir referansı gösteren nesneleri kullanılıyor düşüncesiyle silmez. Peki nasıl bu nesneleri garbage collector'un silebileceği hale getirebiliriz. Bu tarz bir sorunda kullanılmayan nesne referansları için null ataması yapmak, garbage collector'a "Bu nesne ile işim bitti bunu temizleyebilirsin" demek ile aynı anlama gelir.

public Object pop() {
    if (size == 0) {
        throw new EmptyStackException();
    }

    Object result = elements[--size];
    elements[size] = null; // Eliminate obsolete reference  

    return result;
}

Örnekte gösterilen null olarak işaretleme adımı sayesinde garbage collector sonraki tetiklenmesinde artık atıl duruma geçen bu nesneleri herhangi biri referans vermediğinden dolayı silebilecektir. Bahsi geçen bu null ataması bizlerin yapmak istemeyeceği türden istisna bir durumdur. Bunu yapıyoruz çünkü örneğimizde Stack sınıfı içerisinde yer alan element dizisi kendi bellek yönetimini yapmaktadır. Bu durumda 0 ile dizi size değeri arasında kalan kısım aktif bölge olarak ifade edilir. Stack içerisinde aktif bölge (active portion) eklemeler ile arttırılıp, eksiltmeler ile azaltıldığında size değişeceğinden çıkarılan nesnelerin size değerine eşit veya büyük olan referanslar aktif olmayan bölge (inactive portion) olarak adreslenmektedir. Garbage collector bu aktif olmayan bölgeleri yakalayamaz bu yüzden aktif olmayan bölgeler için null ataması yapmamız gerekdi. Aslında bu tarz kullanılmayan referanslardan kurtulmanın en güzel yolu scope (kapsama alanı) yapısıdır. Boşa çıkmış referansı içeren değişkeni kapsam dışında bırakmak gerekir. Her bir değişkeni mümkün olan en dar kapsamda tanımlarsanız kullanılmayan değişkenler doğal olarak kapsam dışında kalacak ve garbage collector tarafından yakalanıp temizlenecektir.

Özetle, kendi bellek yönetimini yapan sınıflarda memory leak olma ihtimalini sürekli göz önünde bulundurmanız gerekiyor. Bir öğe serbest bırakıldığında, öğede bulunan tüm nesne referansları sıfırlanmalıdır.

Son olarak diğer bir yaygın memory leak kaynağı cache'lerdir. Bir nesne referansını bir cache'e koyduğunuzda, onun orada olduğunu unutmak çok kolaydır. Akış içerisinde de özel bir araştırma yapmadığınız sürece bu cache'in kullanılmadığını tespit etmek zordur. Cache keylerinin WeakHashMap üzerinde veya herhangi bir caching system ( örn: memcache) sayesinde zaman bazlı tutmanız ile cachelerin kullanılmadığı durumda silineceğinin garantisini elde etmiş olursunuz.

JavaEffective JavaCreating and Destroying ObjectsObsolete Object ReferencesMemory LeaksGarbage CollectorOutOfMemoryError