Bellek Performansını Verimli Kullanma Yöntemleri

1. Lazy yöntemleri tercih edin

ümit Samimi
4 min readApr 12, 2023

Java8 API dokümantasyonunda aşağıdaki ifadeler yer almaktadır

Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.

Map, Filter gibi yöntemler terminal operasyonlara kadar çalışmaz. Stream dönmeye devam eder. Sebebi iste performanstır.

Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

Benzer şekilde nesne oluştururken de lazy bir yaklaşım sergilenebilir

private List<Integer> numbers;
public List<String> getNumbers() {
if (numbers == null) {
numbers = new ArrayList<>();
}
return numbers;
}

2. Eğer boyutunu biliyorsanız, Collection yerine Array kullanın

Collection’lar dinamik resize yaparlar. Bu önemli sayılabilecek bir maliyettir. Eğer size biliniyorsa, collection yerine dizi kullanılabilir. Eğer size bilinmiyorsa, yapılacak işlemlere göre tercih yapılmalıdır.

  • Eğer search işlemi yapılacaksa, array list tercih edilmelidir. Çünkü array list, index üzerinden arama yapar (o(1)). Linkedlist ise (o(n)) ‘dir.
  • Remove işleminde ise tam tersidir. Linked list (o(1)) ‘dir fakat Array list’te silinen her eleman sonrasında, geri kalan liste reindex edilir. LinkedList silme işlemi hızlıdır çünkü linkedList’teki her bir item birbirine pointer ile bağldırı ve aralarında bir item’ın silinmesi sadece o bağlı olan iki item’ı etkileyecektir.

3 . ThreadPool kullanın

Eğer thread oluşturacaksanız, size en uygun thread pool’u kullanın. Sınırsınız miktarda thread oluşturamazsınız. Oluştursanız bile hepsi paralel çalışmaz. Uygulamanın çalıştığı makinanın coreSize’ını aşacak sayıda oluşturulan thread, işinize yaramaz. Tam tersine başka problemler oluşturur

  • FixedThreadPool
  • CachedTreadPool
  • ScheduledThreadPool
  • SingleThreadedExecutor

4. Transaction’ları readOnly olarak yönetin

Hibernate ve benzeri JPA spesifikasyonları, ‘managed’ statüsündeki yani persistence context’e eklemiş durumda olan varlık(entity) nesnelerini kontrol eder. Bir varlık nesnesi load edildiğinde, o varlık nesnesinin tüm propertiy’lerinin bir kopyası oluşturulur. Flush time dediğimiz senkronizasyon anında ise varlık nesnesinin property’’leri ile load edilen nesnenin property’leri eşleştirilir ve farklılık var mı kontrol edilir. Bu işleme “Dirty Context Check” denir.

Hibernate her defasında, son loaded snapshot ile entity nesnesinin property’lerini kontrol eder. Değişiklik varsa, update işlemini gerçekleştirir. Bu yüzden bu duruma “Hibernate Dirty Check” denir. Aslında bir performans kaybına sebebiyet vermektedir.

Bir entity load edildiğinde, ‘hibernate dirty checking mechanism” ile mevcut entity ile load edilen entity’nin snapshot’ını kıyaslıyor fakat update işlemi yapmayacağımız zaman bu kıyaslama işlemini de kapatabiliriz.

@Transactional(readOnly = true)

İşlem yaptığımız metodu readOnly = true olarak işaretlersek, herhangi bir update işlemi olmayacağı için ‘hibernate dirty check’ işlemi de yapılmaz. Bu da bize performans sağlar.

Eğer spring kullanmıyorsanız, aşağıdaki şekilde de FlushType’ı set edebilirsiniz.

Session session = entityManager.unwrap(Session.class);
session.setDefaultReadOnly(true);

Detaylı bilgi için tıklayınız

5. Primitive Veri Tiplerini kullanın

Mümkün olduğunca Primitive (ilkel) Veri Tiplerini tercih edin. Nesneler hafızada daha fazla yer kaplar. Garbage Collector’ın bir nesneyi heap’ten çıkarması için referansının bir yeri göstermiyor olması gerekir ve bu bir miktar zaman alır fakat ilkel değişkenler stack’te tutulduğundan çok daha fazla performans sağlar. Detaylar için tıklayabilirsiniz

boolean EXIST= true;     // Boolean yerine boolean
int FIRST_INDEX= 0; // Integer yerine int
double PI= 3.14; // Double yerine double

6. Eliminate obsolete object references

Java’da memory’i daha doğru kullanmak adına önemli bir konu işlenmiş Effective Java kitabında. Garbage Collector çalıştığında, kullanılmayan nesnelerin referanslarını heap’ten temizleyebilmesi için bizim de kod yazarken bazı hususlara dikkat etmemiz gerekiyor.

// 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) {
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
}

Java Memory Model isimli yazımda Stack’in ne olduğundan bahsetmiştim. Yukarıdaki kod parçacığında da bir stack örneği oluşturulmuş fakat pop methodu çağrıldığında, elements dizisinin kendisi, size’ı bir azaltılmış hali ile dönülüyor.

Dizi eğer 10 elemanlı ise, siz bu yöntem ile 10 indexli item’ın referansını tutmaya devam edip, 9 nolu indexi geriye dönüyorsunuz. 10 nolu index artık listede olmasa da referans olarak memory’de tutulmaya devam ediyor.

Çözüm aslında basit

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

1 azalttığımız diziyi yeni bir nesneye atamasını yapıyoruz. Mevcut diziye null ataması yapıyoruz. Sonra yeni oluşturduğumuz nesneyi dönüyoruz.

7. İhtiyaç olmadıkça yeni nesne oluşturmayın

String name = "Umit";
String surname = " Samimi";
String fullName = name + surname;

8. Gereksiz autoboxing işlemi yapmayın

int LAST_PAGE= 100;
Integer LAST_PAGE_NUMBER= Integer.valueOf(LAST_PAGE);
// autoboxing yapmak yerine Integer.valueOf() 'u tercih edin

--

--