Spring Boot 2.2 vs 2.3 vs 2.4 (Java 11)

Geçen sene Spring Boot 2.2.0 release notlarında bahsi geçen performans iyileştirmelerini gözlemledikten sonra elde ettiğim bulguları şu yazı ile paylaşmıştım: Spring Boot 2.1 vs 2.2: Bellek Kullanımı ve Başlangıç Süresi İyileşmiş

https://start.spring.io/ üzerinden an itibariyle seçilebilen Spring Boot versiyonlarını Java 11 ile ayrı ayrı VisualVM ile gözlemleyerek, yeni bir Spring Boot projesi oluştururken yapacağımız seçimlerin performansa ve kaynak kullanımına ne gibi etkileri olabileceğini yeniden gözlemlemeye çalıştım.

LTS versiyon olmadığı için Java 15 ile ilgilenmedim; onun yerine Java 17’yi beklemeyi düşünüyorum. Ayrıca önceki yazıda Java 8’den Java 11’e geçişte bariz bir iyileşme sağlandığı için Java 8 ile de ilgilenmedim.

Gelelim sonuçlara:

Spring Boot2.2.122.3.72.4.1
Final “ayrılmış” bellek (heap size)~383 MB~383 MB~383 MB
Final “kullanılan” bellek (used heap)~67 MB~70 MB~237 MB
Maksimum “kullanılan” bellek~248 MB~214 MB~237 MB
Uygulama başlangıç süresi~9.5 sn~9.7 sn~11 sn

Not: Önceki yazı sırasında gözlemleri farklı bir uygulama üzerinde yaptığımdan buradaki değerlerle karşılaştırmak yanlış olur.

2.2.12’den 2.3.7’ye geçerken bir miktar iyileşir gibi olan maksimum “kullanılan” bellek dışında bir iyileşme gözlemleyemedim. İyileşme gözlemleyememek bir yana, 2.4.1’e giderken tablo bir miktar kötüleşiyor hatta…

Bu yazıda geçen yazıdaki gibi dramatik bir sonuç çıkmadı. Zaten her yeni Spring Boot versiyonu da performans / kaynak kullanımı iyileştirmesi vaat etmiyor. Ben yalnızca geçen sürede bir şeyler değişmiş mi gözlemlemek istedim…

“Gözlem” derken?

Bu iki yazıda yaptığım şeyi özellikle “performans testi” yerine basitçe “gözlem” olarak nitelendiriyorum. Performans testi gibi daha fazla değişkenin faklı bakış açılarıyla ele alınacağı, daha ciddi uğraş gerektiren bir çabaya henüz girmedim.

Diğer taraftan, dikkatinizi çekmek istediğim başka bir nokta var: Örneğin farklı “garbage collector” türleri ve farklı JDK dağıtımları arasında karşılaştırmalar yapmak gibi uğraşlara da girilse, uygulamanın gerçek çalışma zamanı metrikleri de elde edilse, en nihayetinde burada ortaya çıkacak değerler uygulama karakteristiğine göre değişecek ve uygulama gereksinimlerine göre farklı yorumlanmak durumunda kalacak. Örneğin bir “batch” uygulamasının “key” performans metriği başlangıç süresi ve bellek kullanımından ziyade belirli bir zaman aralığında kaç kayıt işlediği olabilir. Benzer şekilde, örneğin bir REST API uygulamasının eş zamanlı kaç isteği, ne kadar bellek kullanarak, ve ortalama kaç milisaniyede işleyebildiği o uygulamanın asıl irdelenmesi gereken metrikleri olabilir. Bunlar gibi metrikleri ölçecek ve değerlendirecek yazılarda görüşmek dileğiyle…

Spring Boot 2.1 vs 2.2: Bellek Kullanımı ve Başlangıç Süresi İyileşmiş 🥳

Spring Boot 2.2.0 güncellemeleri arasında dikkatimi en çok performans iyileştirmeleri çekti; hem bellek kullanımı hem de uygulama başlangıç süresinde iyileştirmeler getirildiği söylenmiş:

Ben de Spring Boot 2.1.4 (Java 8) kullanan bir projemizi -tek fark Spring Boot versiyonu olacak şekilde- önce birkaç kere 2.1.4 ile, sonra birkaç kere de 2.2.0 ile ayağa kaldırdım ve uygulamayı VisualVM ile gözlemledim. Uygulama başlangıç zamanı / CPU kullanımı anlamında farkedilebilir bir iyileştirme gözlemleyemedim, diğer taraftan bellek kullanımının bir hayli iyileşmiş olduğunu gördüm:

Spring Boot2.1.42.2.0Fark
Final “ayrılmış” bellek (heap size)~1.6 GB~1 GB-37.5 %
Final “kullanılan” bellek (used heap)~600 MB~240 MB-60.0 %
Maksimum “kullanılan” bellek~630 MB~450 MB-28.5 %
Uygulama başlangıç süresi~75 sn~75 sn0

Bu değerler, ayağa kalkmasının üstünden kısa bir vakit geçmiş, fakat henüz hiçbir istek karşılamamış uygulamalardan alındı. Daha gerçekçi bir test, çalışma zamanı davranışlarını daha uzun vadede incelemeyi gerektirir elbette. Ancak yine de bu gördüklerim beni etkilemeyi başardı…

ÖNEMLİ GÜNCELLEME: Eğer Java 11’e geçerseniz…

Yukarıdaki testi Java 8 ile, sadece Spring Boot versiyonunu değiştirerek gerçekleştirmiştim. Java 11 ile test ettiğimde ise gözlerime inanamadım. Spring Boot 2.2.0 ve Java 11 ikilisiyle, Spring Boot 2.2.0 ve Java 8 ikilisine göre hem bellek kullanımında hem de uygulama başlangıç süresinde yaklaşık yarı yarıya iyileşme sağlandı…

Madem Java 11 ile böyle bir iyileşme sağlandı, belki de Java 8’den sonraki LTS (Long-term support) versiyon olan Java 11’e geçmek için güzel bir bahane bulmuş olabiliriz:

Bootiful günler dilerim 🤓

Ne Olup Bittiğinden Habersiz Testlere Derman: MocksCollector

Test yazmanın en büyük getirisi yazılan koda dair hızlıca geri bildirim alabilmek:

(bkz: https://ufukuzun.wordpress.com/2017/04/19/unit-testing-sunumu/)

Test yazmaya başlayacak yada yeni başlamış geliştiricilerin aklındaki ilk soru genelde şu şekilde oluyor: “Ne kadar test yazmalıyız / nerede durmalıyız / hedefimiz sadece %100 coverage (kapsama) mı olmalı?” — Örnekler çoğaltılabilir. Bu soruların cevabının test yazmanın temel getirisi olan geri bildirim almakta saklı olduğunu düşünüyorum. İdeal durumda testlerimiz öyle kapsayıcı olmalı ki, yapılan her geliştirmeye doğru geri bildirimler vermeli, ve testler geçiyorsa canlıya çıkmaya hazırdır denecek kadar içimizi rahat ettirmeli. İşte tam bu noktada karşımıza “kaliteli test” kavramı çıkmakta. Her geliştirmemize karşılık doğru geri bildirimler veren, deyim yerindeyse arkamızı kollayan testlere “kaliteli”dir diyebiliriz. %100 branch/line coverage bizi bu hedefe yaklaştırsa da çoğu zaman yeterli değildir.

Test kalitesini arttırma çabasına bir örnek olması açısından yaklaşık 5 sene önce n11’de çalışırken karşılaştığım bir test kalitesi sorunsalını çözümeye çalışırken uyguladığım yaklaşımı sizlere paylamak istiyorum:

MocksCollector

Java ile web uygulaması geliştirenlerin neredeyse vazgeçilmezi haline gelmiş Spring çatısı ile geliştirme yaparken Controller / Service / Repository katmanlarına ayrılmış bir şekilde kodlarımızı yazarız. “Service” katmanında ilgili domainin “business logic”leri kodlanır, ve bu sırada ilgili domainin “Repository”si ve pek çok başka “Service” enjekte edilerek (@Autowired) ve kullanılarak business logic gerçeklenir. Bu servisler zamanla büyür, büyüdükçe belli ilgilere göre metotlar gruplanarak başka serviscikler meydana getirilir, böyle devam eder gider.

Şöyle bir durumla karşı karşıyaydım: Geliştirme yapacağım servis 10’dan fazla servisi kullanılıyordu. Bu servislerden bir kaçının farklı şekillerde kombine edilerek çağrıldığı birden fazla public metoda sahipti. Bu metotların birim testleri de yazılmıştı, ve “coverage” %100’dü. Ben de farklı bir business logic için çağrılacak yeni bir metot ekleyecektim. Ekledim de. Fakat testini yazarken şunu farkettim: Metodumda farkında olmadan, aslında benim metodumda çağrılmaması gereken, hatta çağırıldığında çalışma zamanında (runtime) business logic anlamında sıkıntı çıkaracak bir servisi çağırmıştım. Fakat testim bu durumdan habersizdi. Güvendiğim dağlara yazıklar olmuştu. Testim geçiyordu fakat kodum yanlıştı. Hemen testimde değişikliğe giderek o an o test sınıfında tanımlı tüm mock servisleri geçtiğim bir verifyNoMoreInteractions(…) satırı ekledim. Böylece verifyNoMoreInteractions(…) öncesinde verify() ile varlığını kontrol ettiğim mock etkileşimleri (interactions) dışında başka bir etkileşim olmadığı konusunda içim rahatladı. Yanlışlıkla başka bir servis ile etkileşime girildiğinde testim bana anında geri bildirimde bulunacaktı. Ancak o sırada aslında bu durumun ne kadar da yaygın olduğunu farkettim. Mock kullanılan neredeyse tüm testlerde bu durum gözden kaçıyordu. verifyNoMoreInteractions() kullanımı bu farkındalık olmadığı için pek de yaygın sayılmazdı.

“Ne Olup Bittiğinden Habersiz Testlere Derman: MocksCollector” yazısını okumaya devam et

Java 8 Sertifikasyonu (OCAJP 8) – Bölüm 1: Java’nın Temelleri

Bu birinci bölümden itibaren sınavda karşımıza çıkacak konulara değinmeye başlıyoruz. Verilen bilgilerin bazıları zaten bildiğiniz şeyler olacak, ancak bir sınava gireceğimizi ve pek çok yanıltıcı soru olacağını unutmayalım. Bazılarına önceleri benim de dikkat etmediğim, sınavda çeldirici olabilecek bazı özel noktaları “DİKKAT!” etiketiyle belirtiyorum. Verilen bilgilerin çoğunu bu kategoriye girmeye aday olanlardan seçmeye çalıştım. Eğer tamamını okumaktan sıkılırım diyorsanız sadece “DİKKAT!” etiketli kısımları okuyabilirsiniz.

Java Sınıf (Class) Yapısı

  • Java sınıflarının iki temel elemanı vardır: metotlar (methods) ve alanlar (fields).
  • Bunlara aynı zamanda sınıfın üyeleri (members) da denir.
  • Metotlar fonksiyon (function) yada prosedür (procedure) olarak da ifade edilebilir.
  • Alanlar değişkenler (variables) olarak da bilinir.
  • Alanlar sahip oldukları değerler itibariyle sınıf örneğinin (instance) durumunu (object’s state) belirlerken, metotlar sınıf örneğinin durumu üzerinde işlem (operation) yapar ve sınıfın davranışlarını tanımlar.
  • Bir sınıf bu sınıf üyelerinin hiçbirini barındırmayabilir, yada birini veya her ikisini barındırabilir. Yani şu üçü de geçerli sınıf tanımlarıdır:
class Animal {
}
class Animal {

    String name;

}
class Animal {

    String name;

    public String getName() {
        return name;
    }

    public void setName(String newName) {
        name = newName;
    }

}
  • Bir başka sıklıkla karşımıza çıkan kod parçaları ise yorumlardır (comments). Yorumlar kod içerisine eklediğimiz açıklamalardır ve derleyici (compiler) tarafından dikkate alınmaz. Tek satırlı, çok satırlı ve Javadoc tipinde yorumlar mevcuttur:

“Java 8 Sertifikasyonu (OCAJP 8) – Bölüm 1: Java’nın Temelleri” yazısını okumaya devam et

Java 8 Sertifikasyonu (OCAJP 8) – Bölüm 0: Giriş

Java 8 sertifikasyon sınavına (OCAJP 8) hazırlanırken aldığım notlardan derlemelerimi içeren yazı dizisine hoş geldiniz. 5 yıldan uzun süredir Java programlama dili ile geliştirme yapan birisi olarak burada paylaşacağım notlar haliyle daha çok “highlights” (önemli noktalar) tadında olacak.

java8-logo

Lafı uzatmadan…

OCAJP 8 nedir?

  • Açılımı “Oracle Certified Associate, Java SE 8 Programmer”.
  • Oracle Sun’ı satın almadan ve kapsamını bölerek iki ayrı sertifikasyon programı haline getirmeden önceki adı “Sun Certified Java Programmer (SCJP)” olan sertifikasyon programının birinci aşaması.
  • Java’nın temel/görece basit konularının ele alındığı bu programın bir büyüğü ise OCAJP 8 (Oracle Certified Professional, Java SE 8 Programmer)
  • Bu iki program da Java SE konularını kapsamaktadır ve Java EE ile ilgili sertifikasyon programlarının ön koşuludurlar.
  • Kapsamı, giriş ücreti (şu sıralar 798 TL / 210$ civarı) ve sınav merkezi lokasyon bilgisi için (Türkiye’de pek çok test merkezi mevcut): http://education.oracle.com/pls/web_prod-plq-dad/db_pages.getpage?page_id=5001&get_params=p_exam_id:1Z0-808

OCAJP 8 konuları?

“Java 8 Sertifikasyonu (OCAJP 8) – Bölüm 0: Giriş” yazısını okumaya devam et

“Unit Testing” Sunumu

EFT Software’de çalışırken, “unit test” yazma konusundaki deneyimlerimizi geliştirme takımının geri kalanıyla paylaşmak için hazırladığımız “Unit Testing & Spring Test Framework – Testing Software From Developers’ Point-of-View” başlıklı sunuma aşağıdaki bağlantıdan erişebilirsiniz:

http://slides.com/ufukuzun/unit-testing-extended

Screen Shot 2017-04-19 at 00.30.54

 

Kodunuza İyi Bakın – IntelliJ ile Bahar Temizliği

Java derleyicisi kodunuzu biçimsel olarak nasıl düzenlediğinize karışmaz, kodunuzun derlenebilmesi için Java söz dizim kurallarına uymanız yeterlidir. Ancak kodunuzun okunabilir olması için kodunuzun biçimlendirmesine (format) dikkat etmeniz gerekir. Özellikle birden fazla kişinin çalıştığı büyük/orta ölçekli projelerde okunabilir kod eşittir bakımı yapılabilir kod denebilir.

Elbette kodun biçimsel düzeni okunabilir/anlaşılabilir kodun tek ön koşulu değil, yine de özenli olma yolunda iyi bir başlangıç olacaktır. (Yeri gelmişken okunabilir/anlaşılabilir kod yazmak üzerine müthiş tavsiyelerde bulunan şu kitaba göz atmanızı şiddetle tavsiye ederim: Clean Code: A Handbook of Agile Software Craftsmanship. Ya da şu yazı dizisine bir bakın derim: Clean Code’dan Notlar: Bölüm 1 — Temiz Kod Derken?)

Özellikle birden fazla kişinin çalıştığı projelerde zaman zaman acele yetişmesi gereken işlerden, kafa dağınıklığından yada basitçe kişisel ihmalden kaynaklı olarak -kodun hacminin de büyümesiyle- kodun biçimsel düzeni bozulmaya başlar. İşte burada bu bozuklukları topluca düzenlemede kullanabileceğiniz basit bir yöntemden bahsedeceğim. Favori Java editörüm olan IntelliJ IDEA‘da şu iki adımla tüm *.java dosyalarımızı topluca düzene sokmamız mümkün:

Adım 1: Tüm Java sınıflarımızı barındıran “src” dizinine (başka türde dosyalar da içerebilir elbette) sağ tıklayıp “Reformat Code” seçeneği seçilir:

reformat-them-all-step-1

Adım 2: Açılan “Reformat Code” penceresinde aşağıdaki seçenekler uygulanıp “Run” denilerek, tüm *.java dosyalarımızın düzenlenmesi yanında kullanılmayan ‘import’ deyimlerinden de arındırılması sağlanır:

reformat-them-all-step-2

Java ve Apache Commons Email Kütüphanesi ile Gmail Hesabından E-posta Göndermek

Java ile e-posta göndermek istiyorsanız standart Java Mail API yerine onun kullanımı basitleştiren Apache Commons Email kütüphanesini kullanmak yerinde olacaktır.

Aşağıdaki bağımlılığı pom.xml’imize ekleyerek işe başlayalım:

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-email</artifactId>
   <version>1.4</version>
</dependency>

Kütüphanenin bu yazıyı okuduğunuz andaki güncel versiyonunu öğrenmek için: http://mvnrepository.com/artifact/org.apache.commons/commons-email

E-posta gönderimini gerçekleştirebileceğiniz kodumuz ise şu şekilde:

Email email = new SimpleEmail();
email.setHostName("smtp.gmail.com");
email.setSmtpPort(587);
email.setSSLOnConnect(true);
email.setAuthenticator(new DefaultAuthenticator("ufukuzun.ce@gmail.com", "** Gmail parolanız **"));
email.setFrom("ufukuzun.ce@gmail.com");
email.addTo("ufuk.uzun@hotmail.com");
email.setSubject("Test Subject");
email.setMsg("Test Message");
email.send();

DefaultAuthenticator kurucusuna kendi Gmail bilgilerinizi girmelisiniz.

Örnek e-posta içeriğim düz metin (“Test Message”) olduğu için Email nesnesi olarak bir SimpleEmail sınıfı örneği kullandım. E-posta içeriğiniz HTML içerik olacaksa basitçe HtmlEmail sınıfını örneklemelisiniz.

Her şey güzel ancak çalıştırdığınızda şöyle bir hata mesajı ile karşılaşacaksınız: “Java ve Apache Commons Email Kütüphanesi ile Gmail Hesabından E-posta Göndermek” yazısını okumaya devam et

Java’da Multithreading – Bölüm 13: Threadlerin Yarıda Kesilmesi (Interrupting)

Bu zamana kadar ki örneklerimizde “InterruptedException” istisnası ile sıkça karşılaştık. Bu istisna bir threadin çalışması yarıda kesildiğinde fırlatılır. Peki çalışmakta olan bir threadin çalışması nasıl yarıda kesilir? Gelin yaptığı işlem uzun süren bir threadi belli süre geçtikten sonra durdurmayı deneyelim. Bunun için “Thread” sınıfının sağladığı “interrupt()” metodunu kullanacağız:

public class Application {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Başladı.");

        Thread t = new Thread(() -> {
            for (int i = 0; i < 1E8; i++) {
                Math.sin(new Random().nextDouble());
            }
        });

        t.start();

        Thread.sleep(500);

        t.interrupt();

        t.join();

        System.out.println("Bitti.");
    }

}

Programın çalışması bittiğinde çıktısının şu şekilde olduğunu göreceksiniz: “Java’da Multithreading – Bölüm 13: Threadlerin Yarıda Kesilmesi (Interrupting)” yazısını okumaya devam et

Java’da Multithreading – Bölüm 12: “Callable” ve “Future”

Thread havuzlarını incelediğimiz bölümde görmüştük: “ExecutorService” sınıfının “submit” metoduna yapılacak işleri birer “Runnable” nesnesi şeklinde geçeriz, o da her bir “Runnable” nesnesi için bir thread oluşturur ve başlatır. “submit” metodu eklediğimiz her bir “Runnable” için bir “Future” döndürür. Bu “Future” nesneleri sayesinde asenkron işletilen bu görevlerin tamamlanıp tamamlanmadığını, yoksa iptal mi edildiklerini kontrol edebilir, hatta o görevleri iptal edebiliriz. Ayrıca yine bu “Future” nesnelerini kullanarak threadlerin dönüş değerlerini elde edebiliriz. Şaşırmış olmalısınız, çünkü “Runnable” arabiriminin “run()” metodu değer döndürmez, yani “void”dir. Değer döndürme ihtiyacımız olduğu durumlarda Java eş zamanlılık kütüphanesinde yer alan “Callable” adlı arabirimi kullanabilir, “submit” metoduna görevlerimizi “Callable” nesneleri olarak geçebiliriz. “Callable” arabirimi jenerik tiplidir ve sahip olduğu tek metot olan “call()” metodu “Callable” jeneriği için belirtilen tipte bir değer döner.

Aşağıda “submit” metoduna rastgele bir sayı dönen bir “Callable” geçtik. Çıktı olaraksa “Future” nesnesi üzerinden elde ettiğimiz bu rastgele sayıyı yazdıracak:

public class Application {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();

        Future future = executorService.submit(new Callable() {
            @Override
            public Integer call() throws Exception {
                Random random = new Random();
                return random.nextInt();
            }
        });

        executorService.shutdown();

        try {
            Integer result = future.get();
            System.out.println("Sonuç: " + result);
        } catch (ExecutionException e) {
            System.out.println("Çalışma zamanı hatası!");
        } catch (InterruptedException e) {
        }
    }

}

“Java’da Multithreading – Bölüm 12: “Callable” ve “Future”” yazısını okumaya devam et