11 Şubat 2020 Salı

Tutumlu Javacının El Kitabı - Rise of the OutOfMemoryException



Java geliştiricileri olarak sistem katmanından oldukça soyut bir ortamda geliştirme yapmaktayız. Hafıza yönetimi için nasıl olsa garbage collector var. Bedava gibi new kullanıyoruz, yeni nesneler oluştururken oldukça cömertiz. Yazdığımız sistem canlıya alınana kadar ağır yük testleri de (bknz JMeter) gerçekleştirmemişsek tehlikenin farkına varamayabiliyoruz. Taa ki java.lang.OutOfMemoryException hatasıyla karşılaşana kadar. İşte o zaman, sistem kaynaklarının sınırsız olmadığı gerçeğiyle yüzleşmek zorunda kalıyoruz. Peki acı gerçekle karşılaşınca neler yapmak gerekli? Bu konuda bir kaç püf noktasından bahsedeceğim. Öncelikle sorunun kaynağını nasıl tespit edeceğimizle başlayalım.


JVisualVM.exe (Java VisualVM)


Öncelikle sistem kaynaklarının ne kadarını kullandığımızı gözden geçirmek için kullanacağımız bir araçtan bahsedelim. JDK kurulu her makinada bulunan gizli kahraman. Windows makinalarda tahmini olarak şu lokasyonda bulunur C:\Program Files\Java\jdk1.8.0_241\bin\jvisualvm.exe
Sistemde anlık olarak koşan java uygulamalarının hafıza kullanımı, thread yoğunluğu, işlemci kullanımı gibi detayları görüntüleyebilmemizi sağlıyor. Heap dump veya Thread Dump alarak oldukça detaylı olarak inceleme yapmamıza yardımcı oluyor.



Monitor tabında cpu ve garbage collector (gc) hareketliliği görüntülenebiliyor. Buradan ne sıklıkla GC çağrıldığı anlaşılabilir. Çok sık GC çağrılıyorsa yakında cpu bu GC'leri yetiştiremeyecek hale gelecek ve OutOfMemoryException hatası ortaya çıkacak demektir. Yine bu ekranda, anlık heap size kullanım miktarı ve canlı thread sayısı grafiksel olarak gözelemlenebilmektedir. Kullanım miktarının yükseliş trendi uygulama hakkında az çok fikir verecektir. Bu arada "Görev Yönetici"sinden işlemin ne kadar hafıza tükettiği bilgisi kişiyi yanıltabilir. Çünkü orada tüm prosesin toplam harcadığı memory miktarı gösterilmektedir, sadece heap space size gösterilmemektedir. JVM sistemden aldğı hafızayı sisteme geri vermekte o kadar da aceleci değildir. Bu nedenle heap space kullanımı ile alakalı en sağlıklı veriyi VisualVM uygulamasından görmek mümkündür.



Thrad tabında, canlı olan threadler görüntülenebilir ve çalışma süresi anormal derecede uzun olan threadler tespit edilebilir.




Sampler tabında CPU ve Memory seçenekleri bulunmaktadır. CPU tıklandığında, anlık olarak işletilen metodlar ve harcadıkları zamanlar görüntülenebilmektedir.



Memory tıklanınca ise, heap size içerisinde yer kaplayan nesneler byte miktarları, sayıları ve yer kaplama yüzdeleriyle listelenmektedir.






Pencerede bulunan "Deltas" tuşu ile anlık artış/azalış miktarları da görüntülenebilmektedir.
Peki olağan şüpheli olarak byte şampiyonu nesneyi burada belirledik diyelim. Bu neseler nerede tutuluyor bilgisine nasıl ulaşacağız? Sağ üstte bulunan tuş ile Heap Dump alarak.



Heap dump ile hafızanın o anlık fotoğrafı çekilmiş oldu. Burada hangi nesneden kaç tane olduğu ne kadarlık byte tükettiği bilgileri görülebilmektedir. Classes tabına gelip bir satıra çift tıkladığımızda o nesnenin "Instances" sayfası gelmektedir.




Burada da seçilen sınıf türünün her bir üyesinin hafızada hangi nesnelerle ilişkili olduğu oldukça detaylı şekilde görülebilmektedir. Burada memory leak tespiti yapmak mümkündür.


Sorunun kaynağını tespit ettikten sonra koda gidip ilgili nesnenin oluşturulduğu yerler incelenmeli. Uygulama işleyişinde artık kullanılmayacak nesnelerin referansları bir yerlerde kalmamalı, referanslar sıfırlanarak Garbage Collector için hazır hale getirilmelidir.

Sık üretilen nesneler varsa geri dönüşüm sistemi oluşturulmalı ve olabildiğince yeni nesne oluşturmaktan kaçınmalıdır. Bunun için Object Pool Design Pattern kullanılabiliyorsa hayata geçirilmelidir.


Object Pool Design Pattern


Nesnelerde sıfır atık politikası! "new" sözcüğünün kullanımını en aza indirmek için tasarlanmış bir desendir. Aynı tip nesneler çok sık üretiliyorsa, bu nesneler bir havuzda biriktirilir, ihtiyaç oldukça bu havuzda boş duran nesnelerden kullanılır. Kullanımı biten nesne tekrar havuza döner. Örnek olarak veritabanlarının connection pool mekanizması verilebilir. Amacının tersine işlememesi için havuzun maksimüm ve minimum nesne sayısı iyi belirlenmelidir.

Cached Thread Pool

Yeni thread başlatmak da sistemi yoran pahalı işlemlerdendir. Uygulamanın bir yerinde çok fazla yeni thread başlatılıyorsa bu threadlerin de mümkün olduğunca sadece ihtiyaç duyulduğunda yenisinin oluşturulması sağlanmalıdır. Bu nedenle new Thread(new Runnable () {...}) demek yerine
 
ExecutorService executor = Executors.newCachedThreadPool();

executor.execute(new Runnable() {...})


şeklinde oluşturulabilecek cached bir thread pool kullanılmalıdır. Bu executorService, belli sayıda threadi belli süre canlı tutarak, bir sonraki executa çağrılarında yeni thread oluşturmanın önüne geçerek performans kazanımı sağlamaktadır.



Sizin de hafıza yönetimi ipuçlarınız var ise yorum olarak eklerseniz istifade etmiş oluruz.


Koronavirüs Etrafında Design Patternlar

 Merhabalar. Tasarım desenleri iş mülakatlarında sıkça sorulan sorulardandır. Akılda kalıcı olması açısından gerçek hayattan örneklendirmekt...