Java uygulamaları JVM üzerinde çalışır, ancak JVM teknolojisi hakkında ne biliyorsunuz? Bir serinin ilki olan bu makale, klasik bir Java sanal makinesinin nasıl çalıştığına dair genel bir bakış sunar; örneğin Java'nın bir kez yaz, her yerde çalıştır motorunun artıları ve eksileri, çöp toplama temelleri ve yaygın GC algoritmaları ve derleyici optimizasyonlarının bir örneği . Sonraki makaleler, günümüzün yüksek düzeyde eşzamanlı Java uygulamalarının performansını ve ölçeklenebilirliğini desteklemek için daha yeni JVM tasarımları da dahil olmak üzere JVM performans optimizasyonuna dönecektir.
Eğer bir programcıysanız, o zaman şüphesiz, düşünce sürecinizde bir ışık yandığında, bu nöronlar sonunda bir bağlantı kurduğunda ve önceki düşünce modelinizi yeni bir perspektife açtığınızda, o özel duyguyu deneyimlemişsinizdir. Kişisel olarak yeni bir şeyler öğrenme hissini seviyorum. Java sanal makine (JVM) teknolojileriyle, özellikle çöp toplama ve JVM performans optimizasyonu ile ilgili çalışmalarımda birçok kez bu anları yaşadım. Bu yeni JavaWorld serisinde bu aydınlatmanın bir kısmını sizlerle paylaşmayı umuyorum. Umarım JVM performansını öğrenmek için benim yazdığım kadar heyecanlı olursunuz!
Bu seri, JVM'nin temel katmanları ve bir JVM'nin gerçekte ne yaptığı hakkında daha fazla bilgi edinmek isteyen herhangi bir Java geliştiricisi için yazılmıştır. Yüksek düzeyde, çöp toplama ve çalışan uygulamaları etkilemeden belleği güvenli ve hızlı bir şekilde boşaltma arayışından bahsedeceğim. Bir JVM'nin temel bileşenleri hakkında bilgi edineceksiniz: çöp toplama ve GC algoritmaları, derleyici çeşitleri ve bazı yaygın optimizasyonlar. Ayrıca Java kıyaslamasının neden bu kadar zor olduğunu tartışacağım ve performansı ölçerken dikkate alınması gereken ipuçları sunacağım. Son olarak, Azul'un Zing JVM, IBM JVM ve Oracle's Garbage First (G1) çöp toplayıcısından önemli noktalar da dahil olmak üzere JVM ve GC teknolojisindeki bazı yeni inovasyonlara değineceğim.
Umarım bugün Java ölçeklenebilirliğini sınırlayan faktörleri ve bu sınırlamaların bizi Java dağıtımlarımızı optimum olmayan bir şekilde tasarlamaya nasıl zorladığını daha iyi anlayarak bu diziden uzaklaşırsınız. Umarım biraz aha yaşarsınız ! anlar ve Java için iyi bir şey yapmak için ilham alın: sınırlamaları kabul etmeyi bırakın ve değişim için çalışın! Zaten açık kaynak kodlu bir katkıda bulunmadıysanız, belki bu dizi sizi bu yönde cesaretlendirecektir.
JVM performans optimizasyonu: Seriyi okuyun
- Bölüm 1: Genel Bakış
- Bölüm 2: Derleyiciler
- Bölüm 3: Çöp toplama
- Bölüm 4: Eşzamanlı olarak GC sıkıştırma
- Bölüm 5: Ölçeklenebilirlik
JVM performansı ve 'hepsi için bir' meydan okuması
Java platformunun doğası gereği yavaş olduğu fikrine takılıp kalan insanlara haberlerim var. JVM'nin zayıf Java performansından sorumlu olduğu inancı onlarca yıldır - Java kurumsal uygulamalar için ilk kez kullanıldığında başladı ve modası geçmiş! Bu iseFarklı geliştirme platformlarında basit statik ve deterministik görevler çalıştırmanın sonuçlarını karşılaştırırsanız, bir JVM de dahil olmak üzere herhangi bir sanallaştırılmış ortamı kullanmaya göre makine için optimize edilmiş kod kullanarak büyük olasılıkla daha iyi yürütme göreceksiniz. Ancak Java performansı, son 10 yılda büyük adımlar attı. Java endüstrisindeki pazar talebi ve büyüme, bir avuç çöp toplama algoritması ve yeni derleme yeniliği ile sonuçlandı ve JVM teknolojisi ilerledikçe birçok buluşsal yöntem ve optimizasyon ortaya çıktı. Bazılarını daha sonra bu dizide tanıtacağım.
JVM teknolojisinin güzelliği aynı zamanda en büyük zorluktur: "Bir kez yaz, her yerde çalıştır" uygulamasıyla hiçbir şey varsayılamaz. Bir kullanım durumu, bir uygulama ve belirli bir kullanıcı yükü için optimize etmek yerine, JVM bir Java uygulamasında olup biteni sürekli olarak izler ve buna göre dinamik olarak optimize eder. Bu dinamik çalışma zamanı dinamik bir problem setine yol açar. JVM üzerinde çalışan geliştiriciler, inovasyonları tasarlarken statik derlemeye ve öngörülebilir tahsis oranlarına güvenemezler, en azından üretim ortamlarında performans istiyorsak.
JVM performansında bir kariyer
Kariyerimin başlarında çöp toplamanın "çözülmesinin" zor olduğunu fark ettim ve o zamandan beri JVM'ler ve ara yazılım teknolojisine hayran kaldım. JVM'lere olan tutkum JRockit ekibinde çalışarak, kendi kendine öğrenen, kendi kendini ayarlayan çöp toplama algoritmasına yeni bir yaklaşım kodlayarak başladı (bkz. Kaynaklar). JRockit'in deneysel bir özelliğine dönüşen ve Deterministic Garbage Collection algoritmasının temelini oluşturan bu proje, JVM teknolojisi ile yolculuğuma başladı. BEA Systems için çalıştım, Intel ve Sun ile ortaklık kurdum ve BEA Systems'ı satın almasının ardından Oracle tarafından kısa bir süre istihdam edildim. Daha sonra Zing JVM'yi yönetmek için Azul Systems ekibine katıldım ve bugün Cloudera için çalışıyorum.
Makine için optimize edilmiş kod daha iyi performans sağlayabilir, ancak esneklik maliyeti ile gelir; bu, dinamik yüklere ve hızlı özellik değişikliklerine sahip kurumsal uygulamalar için uygulanabilir bir takas değildir. Çoğu işletme, Java'nın yararları için makine için optimize edilmiş kodun mükemmel performansından ödün vermeye isteklidir:
- Kodlama ve özellik geliştirme kolaylığı (yani, pazara daha hızlı giriş)
- Bilgili programcılara erişim
- Java API'leri ve standart kitaplıkları kullanarak hızlı geliştirme
- Taşınabilirlik - her yeni platform için bir Java uygulamasını yeniden yazmaya gerek yok
Java kodundan bayt koduna
Bir Java programcısı olarak, muhtemelen kodlama, derleme ve Java uygulamalarını çalıştırma konularında bilgilisiniz. Örnek vermek gerekirse, bir programınız olduğunu
MyApp.javave onu çalıştırmak istediğinizi varsayalım . Bu programı çalıştırmak için önce onu
javacJDK'nın yerleşik statik Java dilinden bayt koduna derleyicisi ile derlemeniz gerekir . Java kodu dayanarak,
javacilgili çalıştırılabilir bytecode üretir ve aynı adlı sınıf dosyasına kaydeder:
MyApp.class. Java kodunu bayt koduna derledikten sonra, çalıştırılabilir sınıf dosyasını
javakomut satırınızdan veya başlangıç betiğinizden, başlangıç seçenekleriyle veya bu seçenekler olmadan başlatarak çalıştırmaya hazırsınız . Sınıf, çalışma zamanına (çalışan Java sanal makinesi anlamına gelir) yüklenir ve programınız yürütülmeye başlar.
Günlük bir uygulama çalıştırma senaryosunun yüzeyinde olan budur, ancak şimdi bu komutu çağırdığınızda gerçekten ne olduğunu inceleyelim
java. Java sanal makinesi denen bu şey nedir ? Çoğu geliştirici ayarlama sürekli süreç boyunca bir JVM etkileşimde bulunmuş - aka seçerek ve değer atama ustalıkla hata "bellek dışında" rezil JVM kaçınırken, daha hızlı Java programı çalışmasını sağlamak için başlangıç seçenekleri. Peki, Java uygulamalarını çalıştırmak için neden bir JVM'ye ihtiyacımız olduğunu hiç merak ettiniz mi?
Java sanal makinesi nedir?
Basitçe söylemek gerekirse, JVM, Java uygulaması bayt kodunu çalıştıran ve bayt kodunu donanıma ve işletim sistemine özel talimatlara çeviren yazılım modülüdür. Bunu yaparak, JVM, Java programlarının, orijinal uygulama kodunda herhangi bir değişiklik gerektirmeden, ilk yazıldıkları yerden farklı ortamlarda çalıştırılmasına olanak tanır. Java'nın taşınabilirliği, kurumsal bir uygulama dili olarak popülerliğinin anahtarıdır: geliştiricilerin her platform için uygulama kodunu yeniden yazmaları gerekmez çünkü JVM, çeviri ve platform optimizasyonunu yönetir.
JVM, temelde, altta yatan katmanlarla etkileşim yoluyla yürütme görevlerini atarken ve bellek işlemleri gerçekleştirirken, bayt kodu talimatları için bir makine görevi gören sanal bir yürütme ortamıdır.
Bir JVM, Java uygulamalarını çalıştırmak için dinamik kaynak yönetimini de üstlenir. Bu, bellek ayırmayı ve ayırmayı, her platformda tutarlı bir iş parçacığı modelini sürdürdüğü ve yürütülebilir talimatları uygulamanın yürütüldüğü CPU mimarisine uygun bir şekilde organize ettiği anlamına gelir. JVM, programcıyı nesneler arasındaki referansları takip etmekten ve sistemde ne kadar süre tutulması gerektiğini bilmekten kurtarır. Ayrıca, hafızayı boşaltmak için tam olarak ne zaman açık talimatlar yayınlayacağımıza karar verme zorunluluğundan da kurtarır - C gibi dinamik olmayan programlama dillerinin kabul edilen bir sorun noktası.
JVM'yi Java için özel bir işletim sistemi olarak düşünebilirsiniz; görevi, Java uygulamaları için çalışma zamanı ortamını yönetmektir. Bir JVM, temelde, altta yatan katmanlarla etkileşim yoluyla yürütme görevlerini atarken ve bellek işlemlerini gerçekleştirirken, bayt kodu talimatları için bir makine görevi gören sanal bir yürütme ortamıdır.
JVM bileşenlerine genel bakış
JVM dahili bileşenleri ve performans optimizasyonu hakkında yazılacak çok şey var. Bu dizide gelecek makalelerin temeli olarak, JVM bileşenlerine genel bir bakışla bitireceğim. Bu kısa tur, özellikle JVM'ye yeni başlayan geliştiriciler için yararlı olacak ve serinin sonraki bölümlerinde daha derinlemesine tartışmalar için iştahınızı açacaktır.
Bir dilden diğerine - Java derleyicileri hakkında
Bir derleyici girdi olarak bir dili alır ve çıktı olarak çalıştırılabilir bir dil üretir. Bir Java derleyicisinin iki ana görevi vardır:
- Java dilinin, ilk yazıldığında herhangi bir özel platforma bağlı kalmadan daha taşınabilir olmasını sağlayın
- Sonucun, amaçlanan hedef yürütme platformu için verimli yürütme kodu olduğundan emin olun
Derleyiciler statik veya dinamiktir. Statik derleyiciye bir örnek
javac. Java kodunu girdi olarak alır ve onu Java sanal makinesi tarafından çalıştırılabilir bir dil olan bayt koduna çevirir. Statik derleyiciler , girdi kodunu bir kez yorumlar ve çalıştırılabilir çıktı, program çalıştırıldığında kullanılacak formdadır. Girdi statik olduğu için her zaman aynı sonucu göreceksiniz. Yalnızca orijinal kaynağınızda değişiklik yaptığınızda ve yeniden derlediğinizde farklı bir sonuç göreceksiniz.
Just-In-Time (JIT) derleyicileri gibi dinamik derleyiciler , bir dilden diğerine çeviriyi dinamik olarak gerçekleştirir, yani kod yürütülürken bunu yaparlar. Bir JIT derleyicisi, çalışma zamanı profil oluşturma verilerini (performans sayaçlarını ekleyerek) toplamanıza veya oluşturmanıza ve eldeki ortam verilerini kullanarak anında derleyici kararları almanıza olanak tanır. Dinamik derleme, talimatları derlenmiş dilde daha iyi sıralamayı, bir dizi talimatı daha verimli setlerle değiştirmeyi ve hatta gereksiz işlemleri ortadan kaldırmayı mümkün kılar. Zamanla daha fazla kod profili oluşturma verisi toplayabilir ve ek ve daha iyi derleme kararları alabilirsiniz; bu genellikle kod optimizasyonu ve yeniden derleme olarak adlandırılır.
Dinamik derleme, yeni optimizasyon ihtiyacını tetikleyen zaman içinde davranış veya uygulama yükündeki dinamik değişikliklere uyum sağlama avantajı sağlar. Dinamik derleyicilerin Java çalışma zamanlarına çok uygun olmasının nedeni budur. Buradaki sorun, dinamik derleyicilerin profil oluşturma ve optimizasyon için fazladan veri yapıları, iş parçacığı kaynakları ve CPU döngüleri gerektirebilmesidir. Daha gelişmiş optimizasyonlar için daha fazla kaynağa ihtiyacınız olacak. Ancak çoğu ortamda, kazanılan yürütme performansı iyileştirmesi için ek yük çok küçüktür - saf yorumlamadan elde edeceğinizden beş veya 10 kat daha iyi performans (yani, bayt kodunu olduğu gibi, değişiklik yapmadan yürütme).
Tahsis, çöp toplamaya neden olur
Tahsis , Java öbek veya kısaca öbek olarak da bilinen her "Java işlemine ayrılmış bellek adres alanında" iş parçacığı başına yapılır. Tek iş parçacıklı ayırma, Java'nın istemci tarafı uygulama dünyasında yaygındır. Tek iş parçacıklı ayırma, kurumsal uygulamada ve iş yüküne hizmet eden tarafta hızla optimal olmaz, ancak modern çok çekirdekli ortamlarda paralellikten yararlanmamaktadır.
Parallell uygulama tasarımı aynı zamanda JVM'yi birden çok iş parçacığının aynı adres alanını aynı anda tahsis etmemesini sağlamaya zorlar. Bunu, tüm tahsis alanına bir kilit koyarak kontrol edebilirsiniz. Ancak bu tekniğin (sözde yığın kilidi ) bir maliyeti vardır, çünkü iş parçacıkları tutmak veya sıraya koymak, kaynak kullanımı ve uygulama performansında bir performans düşüşüne neden olabilir. Çok çekirdekli sistemlerin bir artı yanı, tek iş parçacıklı, serileştirilmiş tahsisin darboğazını önlemek için kaynak tahsisine yönelik çeşitli yeni yaklaşımlar için bir talep yaratmalarıdır.
Yaygın bir yaklaşım, yığını çeşitli bölümlere bölmektir; burada her bölüm uygulama için "uygun boyuttadır" - açıkça, ayırma hızı ve nesne boyutları farklı uygulamalar için ve aynı zamanda iş parçacığı sayısı. Bir İş Parçacığı Yerel Ayırma Tamponu (TLAB) veya bazen İş Parçacığı Yerel Alan(TLA), bir iş parçacığının tam bir yığın kilidi talep etmek zorunda kalmadan içinde serbestçe tahsis ettiği ayrılmış bir bölümdür. Alan dolduğunda, yığın ayrılacak alan kalmayana kadar iş parçacığına yeni bir alan atanır. Yığını ayırmak için yeterli alan kalmadığında "dolu" dır, yani öbek üzerindeki boş alan, ayrılması gereken nesne için yeterince büyük değildir. Yığın dolduğunda, çöp toplama devreye girer.
Parçalanma
TLAB kullanımıyla ilgili bir sorun, yığını parçalayarak bellek verimsizliğine neden olma riskidir. Bir uygulama, bir TLAB boyutuna kadar eklemeyen veya tam olarak tahsis etmeyen nesne boyutlarını ayırırsa, yeni bir nesneyi barındırmak için çok küçük çok küçük bir boş alanın kalması riski vardır. Bu kalan boşluğa "parça" adı verilir. Uygulama ayrıca bu kalan alanların yanında tahsis edilen nesnelere referanslar tutarsa, alan uzun süre kullanılmadan kalabilir.