Press "Enter" to skip to content

Effective Java Madde 7: Erişilmeyen Nesnelerin Referanslarından Kurtulun

Last updated on September 11, 2019

C veya C++ gibi bellek yönetimini yazılımcının yaptığı dilleri kullandıktan sonra Java gibi çöp toplayıcı (garbage collector) mekanizmasına sahip bir dile geçiş yaptığınız zaman, yazılımcı olarak işinizin ne kadar kolaylaştığını farkedersiniz çünkü çöp toplayıcı sizin için bellekte kalmış kullanılmayan nesneleri temizleyecektir. Bu durum size Java ile kodlama yaparken bellek yönetimini düşünmek zorunda olmadığınız izlenimini verebilir ancak bu doğru değildir!

Hemen aşağıdaki yığıt (stack) kodunu inceleyelim:

// Bellek sızıntısını bulabilir misiniz?
public class Stack {

    // Yığıt yazarken generic türler kullanmak daha mantıklı olacaktır
    // ama buradaki amacımız farklı olduğu için üzerinde durmuyoruz
    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];
    }

    /**
    * Dizide boşluk kalmamışsa, bir sonraki elemanın eklenebilmesini
    * sağlamak için diziyi genişletiyoruz
    */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

İlk bakışta bu programda hiçbir hata yokmuş gibi görünüyor (yalnız daha doğru olan generic versiyonu için Madde 29’a bakın) Bu kodu test ettiğiniz zaman da bütün teslerden geçecektir ve istediğiniz gibi çalışacaktır. Ancak bu kod içerisinde kolay kolay farkedilmeyen bir “bellek sızıntısı” mevcut. Bellek sızıntıları çok sinsi bir şekilde zaman içerisinde etkisini artırarak, uygulamanın giderek daha fazla bellek tüketmesine ve çöp toplayıcının daha sık tetiklenmesine sebep olurlar. Ciddi boyuttaki bellek sızıntıları belirli bir süre sonra bellek yetersizliği yüzünden uygulamanın tamamen çökmesine bile sebep olabilir.

Peki bellek sızıntısı nerededir? Burada yığıtın boyutu önce artıp daha sonra azalırsa, yığıttan çıkartılan elemanlar çöp toplayıcı tarafından temizlenemeyecektir. Çünkü bu kod yığıttan çıkarılan nesnelerin referanslarını hala saklamaktadır. Çöp toplayıcı eğer bir nesneye işaret eden referans varsa, o nesnenin hala erişilebilir olduğunu düşünecek ve nesneyi bellekte bırakacaktır. Aslında yığıttan çıkarttığımız nesneye yukarıdaki sınıfı kullanarak tekrar erişmemiz mümkün değildir, ancak nesne referansı hala saklandığı için bellekten temizlenmeyecektir.

Erişilmediği halde başıboş bir biçimde bellekte kalan nesneler zincirleme bir etki yaratacağı için uygulamaya etkisi tahmin ettiğinizden daha büyük olabilir. Bellekte kalan bu nesneler kendi içerisinde başka nesnelere de referanslar tutacağı için, çok sayıda nesnenin çöp toplayıcı tarafından temizlenmesini engellerler. Yani birkaç tane nesne referansını gözden kaçırmak, aslında çok daha fazla sayıda nesnenin bellekten temizlenmesine engel olabilir, bu da ciddi sorunlara sebebiyet verebilir.

Bu tarz problemleri çözmek için dikkatli olmak ve kullanılmayan referanslara null değerini atamak yeterli olacaktır. Yukarıdaki örneği göz önüne alırsak, pop() metodunun aşağıdaki gibi değiştirilmesi sorunu çözecektir.

public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;    
    return result;
}

Burada artık ihtiyacımız olmayan referansa null değerini atadık ve böylece yığıttan çıkartılmış olan nesneye işaret eden referanstan kurtulmuş olduk. Bu durumda çöp toplayıcı bir sonraki sefer tetiklendiğinde bu nesneye ait hiçbir referans kalmadığını görecek ve bellekten silecektir.

Şunu belirtmekte çok büyük fayda var: bir nesne referansını null atayarak işlevsiz hale getirmek genellikle uygulanan bir yöntem değil, tam tersine istisnai bir durumdur. Kullanılmayan nesne referanslarından kurtulmanın en güzel yolu, bu referansların kapsama alanından (scope) çıkmasını sağlamaktır. Her referansı mümkün olan en küçük kapsama alanı (scope) içerisinde tanımlarsanız bu otomatikmen gerçekleşmiş olur. (Madde 57)

Peki o zaman hangi durumlarda nesne referanslarına açık bir şekilde null değerini atamamız gerekir? Yukarıda verdiğimiz yığıt örneğinde neden bunu kendimiz yapmak zorunda kaldık? Bunun sebebi yığıt sınıfının belleği kendisinin yönetmeye çalışmasıdır. Burada yönetilen bellek elements dizisi içerisindeki nesne referanslarıdır. Yığıt içerisinde o anda aktif olarak bulunan referanslar (indeksi 0 ile size değeri arasında kalan referanslar) erişilebilen bellek öğelerine işaret eder, indeksi size değerine eşit veya daha büyük olan referanslar ise artık erişilemeyen, yığıttan çıkartılmış ve dolayısıyla artık başıboş kalmış nesnelere işaret etmektedir. Ancak bu nesnelerin başıboş kaldığını çöp toplayıcının anlamasına imkan yoktur, bu yüzden bizim açıkça bu referanslara null değeri atayarak geçersiz hale getirmemiz gerekir.

Genel olarak konuşmamız gerekirse, eğer bir sınıf kendi içerisinde bellek yönetimi yapıyorsa o sınıfta bellek sızıntılarına karşı dikkatli olmamız gerekir. Böyle bir durumda eğer bir nesne artık erişilemiyorsa, o nesneye işaret eden referansa null değeri atanmalıdır.

Bellek sızıntılarının ikinci bir nedeni önbellekte (cache) unutulan nesne referanslarıdır. Unutulan nesne referansları önbellekteki nesne geçersiz kalsa bile nesnenin sonsuza kadar bellekte kalmasına sebep olacaktır. Bu sorunu aşmak için çeşitli yöntemler kullanılabilir. Bunlardan birincisi eğer önbellek ihtiyacınızı karşılıyorsa normal HashMap yerine WeakHashMap kullanmak olabilir. Bazı durumlarda da önbellekte belirli bir süre erişilmemiş nesneleri temizleyerek yer açmak mümkün olabilir. LinkedHashMap sınıfının removeEldestEntry metodu bu durumda size yardımcı olabilir. Bunların dışında bir önbellek kütüphanesi (Memcache gibi) kullanarak, nesneleri önbelleğe yazarken ne kadar süreyle bellekte kalacağını belirleyebilirsiniz. Belirlediğiniz süre dolduktan sonra nesne bellekten otomatik olarak temizlenecektir.

Bellek sızıntıları programdaki diğer hatalar gibi kolaylıkla kendini göstermeyeceği için yıllar boyunca farkedilmeyebilir. Ancak dikkatli kod okumak ve bir takım performans ölçüm araçlarının yardımıyla bulunabilirler. Bu sebeple bellek sızıntılarının hangi durumlarda ortaya çıkabileceğini bilip ona göre kod yazmak önemlidir.

Share

2 Comments

  1. deneme
    deneme June 8, 2016

    gayet guzel bir yazi, tesekkurler.

Leave a Reply

%d bloggers like this: