26 Ekim 2016 Çarşamba Merhaba değerleri Java dostları, bu yazımda adını sıkça duyduğumuz JIT compiler (Just In Time) hakkında birşeyler yazmak istiyorum. Compiler nedir olaylarına hiç girmek istemiyorum; çünkü bence bu ayrı bir yazı olabilecek kadar geniş bir konu. Bundan dolayı bu yazımda daha çok JIT compiler konusuna yoğunluşmak istiyorum. Malumunuz her yazılım dili bir compiler aracılığı ile makine diline dönüştürülür. Java gibi WORA – Write Once Run Anywhere – diller ise bu işi ara bir katman ile yapar. Java’ da yazdığımız kodun çalışma süreci şöyledir. Kod —> javac —> bytecode —> JVM —> OS Yazdığımız kod javac derleyici ile bytecode formatına dönüştürülür. Daha sonra bu bytecode JVM tarafından OS bağlı koda dönüştürülür ve çalıştırılır. İşte bu noktada Java’ nın WORA olmasını sağlayan bytecode mantığı ve JVM’ dir. Burada alt bir başlık daha açmam gerekiyor. Compilerlar static ve dynamic olmak üzere gruplandırılabilir. Static compiler: Girdi olarak aldığı kod değişmediği müddetçe hep aynı çıktıyı üreten compiler türüdür. Dolayısıyla daha hızlı çalıştırma ve kod üretme yeteneğine sahiptirler. Yukarıda verdiğim akışta javac, static bir derleyicidir. Dynamic compiler: Girdi olarak sadece kod değil aynı zamanda kullanıcı alışkanlığı, kullanım sıklığı gibi başka parametreleri de alan derleyici tipidir. Static compiler ile karşılaştırınca biraz daha yavaş kod üretme süreci olabilir; ama runtime anında daha çok verim ve performans sunarlar. JIT compiler buna örnek verilebilir. Şimdi biraz daha JIT compiler konusuna şınorkel ile dalalım. Java JIT compiler çalışma anında bytecode bloklarını native koda dönüştürür. Yani direkt platform bağımlı bir hale gelir. Bunu yapmasındaki amaç, çok kullanılan veya çağrılan blokların performansını arttırmaktır. Peki native kod oluşturma süreci neye belirlenir diye soracak olursanız, cevabı JVM’ de saklı. JVM çağrılan metot, bloklar vs için bir call count tutar. Bu call count belli bir eşiği yani threshold aşınca JIT compiler devreye girer kodumuz artık native koda dönüşür ve bytecode ile yer değiştirir. Ayrıca bu işlemin bir geri dönüşü yoktur. Yani native kod yerine tekrar bytecode kullanma ihtimali kalmamış oluyor. Burada eşik değeri parametre aracılığı ile değiştirilebilir. Ayrıca yine belli ayarlarla JIT devre dışı bırakılabilir. JIT compiler çalışma sırasında her processte olduğu gibi thread yapısına ihtiyaç duyar. Kullanılacak thread sayısını da belirtebiliriz; ama dikkatli olunması gereken nokta thread sayısı çok yüksek verilirse bu defa başka processlerin alanı kısıtlanmış olunacaktır. Şimdi son olarak JIT compiler sürecine bakalım. JIT compiler kodu olduğu gibi native kod yapısına çevirmemektedir. Belli bir aşamadan ve iyileştirmelerden geçerek nihai native kod oluşur. Bunlardan bazılarını listelemeye çalıştım. Dead Code: Kullanılmayan metotlar değişkenler JIT compiler tarafından elenir. Böylece boşuna yük alınmamış olunur. Inlining: Her ne kadar kodumuz küçük bloklardan oluşsa da native kod çalıştırılırken bunlar devamlı bir jump anlamına gelir ve maliyetli işlemlerdir. Bundan dolayı JIT compiler bazı blokları iç içe gömerek bu jump adımlarını minimum sayıya indirmeye çalışır. Local Optimization: Blok ve metot içerisindeki küçük parçalarda iyileştirmeler yapılır. Control Flow Optimization: Döngü, if, switch gibi akış ve kontrollerde iyileştirmeler yapılır. Global Optimization: Metotun tamamı dikkate alınarak iyileştirmeler yapılır. Native Code Generation: Son adım olarak native kod oluşur. Java ve JVM’ nin temel ve önemli konularından birisi olduğunu düşündüğüm JIT compiler konusuna değinmeye çalıştım. Umarım faydalı olmuştur. Bol Java’ lı günler dileğiyle… https://twitter.com/farukbozan http://www.farukbozan.com/anajavatica/