Java’nın Performansı: Java Yavaş mı?

Uzun süredir yazmak istiyordum Java’nın performans ve optimizasyon konuları üzerine. Nasip 2014 yılının başınaymış.

Java ilk çıktığı andan bu yana genel olarak “yavaş” olmakla suçlanmakta ya da en azından yavaş oluşundan dem vurulmaktadır. Bu tür ifadelerin bir kısmı ciddi tecrübelere dayanmakla birlikte pek çoğunun ezbere edilen cümleler olduğunu ifade etmekte fayda var.

Öncelikle “yavaş” olmak üzerine eğilelim ve “yavaş” olmaktan ne kastediliyor ona bakalım. Java’nın neyi yavaş? Java ile geliştirilen yazılımlar mı yavaş yoksa dil olarak Java mı yavaş olan? Yoksa ikisi de mi? Şunu teslim edelim ki bir yazılım sisteminin performansı hangi dili ya da teknolojiyi kullandığından ziyade, nasıl bir mimari ile kurgulandığıyla ilgilidir. Donanım ve yazılım altyapısının nasıl kurgulandığı, farklı sub-systemler arası haberleşmeler, ön bellek (cache) kullanımı gibi mimari kararlar, çoğu zaman çok performanslı kod yazmaktan daha önemlidir. Çünkü sağlıklı kurgulanmış bir mimari ile çok mütevazi dil ve araçlar kullanarak, son derece performanslı yapılar oluşturulabilir. Örneğin yüzlerce hatta binlerce sunucunun paralel bir şekilde nasıl çok yüksek bir ölçeklenebilirlik (scalability) oluşturabildiğini ya da  ön bellek kullanımının performansı nasıl arttırdığını görmek isterseniz Facebook’un mimarisine bir göz atın derim. Bu amaçla buraya, buraya, burayaburaya, buraya ve de buraya bakabilirsiniz. Bu şekilde PHP ile yazılmış bir web önyüze, C++ ve Java gibi dillerle yazılmış servis yapılarına ve MySQL üzerinde çalışan bir veri tabanı yapısına sahip dev bir mimarinin, örneğin 2011 sonu itibariyle saniyede 60 milyon sorguyu (query) nasıl karşıladığını, dahası bu sorguların %90’ının daha veri tabanına gelmeden ön bellek (cache) üzerinden nasıl cevaplandığını hayretle öğrenebilirsiniz. Yukarıda verdiğim linklerin sonuncusunda zaten Facebook’dan teknik bir uzman bir strateji olarak, bilenen ve yaygın kurumsal (enterprise) web, uygulama, veri tabanı, dosya vb. sunuculara tonla lisans ücreti ödemek yerine, AR-GE yaparak, kendilerine uygun bir mimariyi nasıl geliştirmeye para ayırdıklarını açıklıyor.

Zaman zaman rastlıyorum. Bir süre önce geliştirilmiş bir uygulama artık performans sıkıntısı yaşamaya başlıyor. Yazılımı geliştirenler biraz sistemi inceleyip, performansını iyileştirici işler yaptıklarında sistemin davranışı çok değişiyor, çok daha performanslı davranmaya başlıyor. Nedir yaptıkları? Muhtemelen dil seviyesinde değil de mimari seviyedeki iyileştirmelerle bu sonuç elde ediliyor. Elbette bu iyileştirmelerin dile uzanan kısımları da var ama çoğunlukla JVM parametrelerinde ya da uygulamanın topolojisine uygulanan değişiklikler, rahatlıkla uygulamanızın performansını çok ciddi boyutta rahatlatabilir.

Biraz da dil olarak Java’nın yavaşlığından ya da performansından bahsedelim. Java gerek CPU kullanımı dolayısıyla da zaman, gerekse bellek tüketimi açısından, selefi olan C ve C++ dillerinden daha problemlidir. Dolayısıyla Java temelde C ve C++ ile kıyaslanmaktadır. (Bunun sebebi de Java’nın da temelde bu iki dil gibi genel amaçlı olma ve özellikle iş yazılımları geliştirmede kurumsal ihtiyaçları karşılama iddiasındadır. İşin açıkçası, bu iddiada Java’nın çok önde oluşu su götürmez bir gerçektir.)  Çünkü bu diller, yapı olarak Java’dan çok farklılar. Bir başka deyişle Java, temelde C/C++’tan elde edilen tecrübe üzerine ve web gibi yeni gelişmelere cevap için geliştirildiğinden, tasarımı sırasında alınan örneğin (işletim sistemi ve donanımdan oluşan) platformdan bağımsız olması gibi özelliklerinden dolayı, tabiatı itibariyle daha yavaş çalışmaya meyilli olması normaldir. Burada “meyilli” kelimesini özenle kullandım çünkü, çıkışından bu yana Java derleyicileri (compiler) ve run-time ortamı olan JVMleri ile ilgili o kadar çok çalışma ve yenilik yapıldı ki, bunlar sayesinde kulağınıza inanılmaz gelecek ama Java kodunuzun, eşleniği olan C/C++ kodu kadar hatta daha hızlı çalışması mümkün hale geldi.

Yapısal olarak Java’yı C/C++’tan ayıran dolayısıyla da yavaş kılma ihtimali yüksek olan özelliklerini sıralamak istersek:

  • Platform bağımsız olması sanırım en önde gelir. Java nihayetinde JVM denen sanal bir makine, bir yazılım içinde çalışıyor, dolayısıyla, platformdaki kaynaklara yani CPU, bellek, threadler, hard disk, soketler vs. erişimi dolaylı yoldan olmaktadır. Bu da tabiati icabı bu erişimleri yavaş kılmakta. Tüm bu sıkıntılara Java’nın platformdan bağımsız olması için katlanıyoruz. Böyle bir durum söz konusu olmadığında Java kaynak kodunu doğrudan native koda derleme imkanınız var. Nitekim JVMlerin pek çoğunda bulunan JIT derleyicisi (Just-in-time compiler), kritik kod bloklarını anında-görüntü modunda, native koda çevirir. Bu durum tabi olarak aslında Java kodunun C kadar hızlı çalışabileceği fikrini aklımıza getiriyor. Facebook’un PHP ile yaptığı da bu aslında, PHP ile yazıp, native koda derleyip, native hızında çalıştırmak, eskiden bu yana bilinen bir taktiğin PHP’ye uygulanması, belli ki işe yarıyor.
  • Java nesne-merkezli bir dildir. Dolayısıyla Pascal ya da C gibi yordamsal dillerde int ya da float gibi basit tiplerle ama bölük-pörçük olarak ifade ettiğimiz yapılar Java’da daima çok daha bütünsel ve anlamlı olarak nesnelerle ifade edilir. Basit tipleri manipule etmek, nesneleri manipule etmekten daima çok daha kolaydır. Çünkü nesneler, bir ya da daha fazla basit tipi bir araya getirir ve bu veriler üzerinde çalışan fonksiyonlara da sahip olur. Bu yüzden nesneleri oluşturmak, yönetmek ve sonunda da bellekten silmek kesinlikle çok daha fazla kaynak isteyen bir iştir. Nesneler tabii olarak basit tiplerden çok daha fazla yer kaplar, nesneleri yazılımın katmanları, veri tabanı gibi diğer alt sistemler arasında gezdirmek daha fazla CPU ve belleğe ihtiyaç duyar. Java’da 8 basit tip dışında her şeyin nesne olduğunu, örneğin String’in bir nesne olduğunu, sıra dışı durumların (exceptions), dizilerin, iş alanı kavramlarının, kısaca her şeyin bir nesne olduğunu düşünün. Nesnelerin bir kalıtım hiyerarşisine sahip olduğunu (siz oluşturmasanız bile Java APIsindeki sınıflarda zaten derin hiyerarşiler vardır) ve nesneler üzerindeki metotların polymorphic olduğunu da düşünün.  Dolayısıyla Java’nın özellikle run-time ortamının daha fazla iş yaptığı ve bunun da Java’yı hantal kılacağı söylenebilir.
  • Java’da devamlı nesne oluşturmak zorunda oluşumuz, zamanı gelince nesneleri ortadan kaldırmak gibi bir derde de sebep oluyor. Malum, metotlarla ilgili run-time yapıları stackde tutulur. Yani run-timeda metotlar açıldıkça, JVM, yerel (local) değişkenlerini kendilerine has bellek alanı olan stackde tutar. Metotlarda oluşturduğunuz değişkenler, Java’nın 8 tane olan primitive tipinden ise, bu tür değişkenlere referans oluşturamadığınızdan yani bu basit türden olan değişkenler farklı metotlara ancak değerleri kopyalanarak geçildiklerinden, bu türden olan değişkenlerin ömrü, en çok içinde tanımlandığı bloğun/metodun ömrü kadardır. (Scope kurallarını hatırlayın.) Ama bir metot içinde nesne oluşturduğunuzda, stack sadece o metodun referansını tutar ve metot bittiğinde referansı temizlenir. Eğer nesnenizi, metot bitmeden farklı metotlara geçmişseniz, aynı nesneye yeni referanslar oluşturmuş olursunuz ve bu durumda o nesnenin ömrü, kendisine ulaşan referansların ömrüyle belirlenir. Yani bir nesneye referans olduğu müddetçe o nesne bellekte kalmaya devam eder. Bu durum da çok bilinen memory leak problemine sebep olur. (Bazılarımızın yaptığı gibi nesne oluşturmamak bellek problemine çare olabilir ama bu durumda da “Java görünümlü C” kullanıyor durumuna düşülecektir.) Özellikle iş uygulamalarının bolca nesne oluşturmaya yatkın yapısı, bu tür uygulamaların nihayetinde performans ve ölçeklenebilirlik problemleri yaşamasına sebep olmaktadır. Dolayısıyla bellek yönetimi Java’da ciddi bir problemdir ve bu hem JVM hem de geliştirilen yazılımlar açısından dikkatli bir şekilde ele alınmalıdır.

Internet’te pek çok yerde Java’nın yavaşlığı hakkında yazılar, benchmarklar ve tartışmalar bulursunuz. Mesela burada ve burada olduğu gibi. Ben, bunca yıllık tecrübemden sonra şunu rahatlıkla söyleyebilirim: Yavaş olan Java programcıları, Java değil. Javacılar olarak, kaliteli ve hızlı çalışacak kod yazmada o kadar geriyiz ki, ben malesef, senelerdir Java kodu yazan kişilerin JVM’in yapısı ya da ne bileyim ArrayList ile LinkedList’in uygun kullanım yerleri hakkında olması gerekenden çok daha az hatta hiç bilgi sahibi olmadıklarına, alışa geldikleri şekilde kod yazdıklarına çok sıklıkla şahit oluyorum. Elalemin saniyede binlerce transactionı başarılı bir şekilde çalıştırdığı Javayla biz yazılım geliştirdiğimizde, çok daha küçük sayılarla ifade edilen yüklerde bile sıkıntı çekebiliyoruz. Facebook’un ön tarafta PHP, arkada MySQL ile sağladığı performansın çok cüzi bir kısmını biz önde Java arkada Oracle DB ile sağlayamıyoruz. Unutmayın, ben bu ülkede “Oracle yavaş” diyenleri bile gördüm.

Şu bir gerçek: En temelde yukarıda saydığımız sebeplerden dolayı, C ve C++ gibi native çalışan dillerin gelişi güzel yazıldıklarında sağladıkları performansı Java’da sağlamak için, Javacıların,  C/C++ yazılımcılarından daha bilgili ve maharetli olması gerekli. Bu anlamda gelişi güzel yazılmış C kodu ile Java kodu arasında performans olarak örneğin 100 kat fark varsa, iyi yazıldıkları durumda bu farkın çok azaldığı hatta Java derleyicilerinin ve JVM’in yapısından dolayı, Java kodunun daha performanslı hale geldiğinden çok sıklıkla bahsedilir. Dolayısıyla Javacılar, mimari, algoritmik karmaşıklık gibi seviylerde çok iyi olmakla birlikte Java dilini de çok verimli kullanmak zorundalar. Aksi taktirde çok sıklıkla karşılaştığımız sıkıntılı durumlara düşmek işten bile değil.

Yazımızı, Java’nın temelde “hızlı çalışma” için ortaya çıkmadığını hatırlatarak son verelim. Javacılar için modulerlik, lowly-coupled, highly-cohesive dolayısıyla da daha rahat değişebilen yapılar ortaya çıkarmak, etkin, hızlı çalışan kod yazmaktan pek tabi daha önemlidir. Ama bu Java kodunun son derece yavaş ve hantal olması anlamına gelmez. Gerektiği kadar hızlı ama düzenli bir mimari ve kod yapısı her zaman tercihe şayandır. Biraz bilgi ve gayret ile son derece hızlı ve ölçeklenebilir Java yazılımları geliştirmek çok da zor değildir.

İyi bir performansa sahip Java kodu için aslında aslında yapmamız gereken ilk şey, bu durum hakkında bir farkındalığa sahip olmaktır.

“Hızlı” Java kodlu günler dilerim 🙂

Bu yazı toplam 2138 defa görüntülenmiştir.