Project Valhalla OpenJDK tarafından yürütülmekte olan bir proje. Java'nın tip sistemi ile ilgili Java programlama dili ve JVM üzerinde yapılabilecek geliştirmelerin araştırıldığı ve adım adım sona yaklaşan bu proje bir takım ilginç yenilikler sunuyor. Bunları:
- Value objects / value classes (Değer nesneleri, değer sınıfları)
- Primitive objects / primitive classes (Primitif nesneler, primitif sınıflar)
- Primitif ve nesne tiplerinin birleştirilmesi
olarak sıralayabiliriz. Tip sistemleri ile ilgili daha teorik bilgi için ilgili yazımıza göz atabilirsiniz.
Güzel de, "value" nedir, "object" nedir biliyoruz ama "value object" neyin nesidir ? Gelin beraber bir göz atalım.
Primitifler, Sınıflar ve Kimlik (Identity)
Java Tip Sistemi; 8 pritimif (byte, short, int, long, float, double, char ve boolean), nesne (object) ve dizi (array) olmak üzere 10 tanımlı tipten oluşur. Primitif tipler ile ifade edilemeyen daha karmaşık veri yapıları (örneğin bir string yani karakter katarı, uzayda 3 koordinatı olan bir nokta) pritimif tipleri bir araya getiren sınıflar (class) ve bunların nesneleri ile modellenebilir. Sınıflara kullanıcı (geliştirici) tanımlı tipler olarak bakılabilir. Primitif tipler ve kullanıcı tanımlı tipler (ve bunların dizileri) ile ihtiyaç duyulabilecek tüm veri yapıları oluşturulup kullanılabilir. Primitifler ve sınıflar (ve onların nesneleri) bir arada çok kullanışlı bir tip sistemi sunsa da ikisi arasındaki temel bir farkı bu yazı açısından vurgulamamız gerekiyor: kimlik ve eşitlik.
Nesnelerin bir "kimliği" olduğundan söz edilebilir. Aynı sınıfın iki nesnesi, tamamen aynı değerleri içerse de, bellekte iki farklı yerde, birbirinden farklı iki nesnedirler; yani bir anlamda kendi kimlikleri vardır. Aynı şeyi primitif tipler için söylemek pek mümkün değil. Yani bir int değişkeninin değeri "5" ise, "hangi 5? oradaki 5 mi? buradaki 5 mi?" diye sormanın bir mantığı yoktur çünkü "5, 5'tir". Şöyle bir metafor daha deneyelim: İki kırmızı duvar; boyutları, malzemeleri ve renk tonları tamamen aynı olsa da, sonuçta 2 farklı duvardır. Ama bunu renkleri için söyleyemeyiz, aynı tonda kırmızı olduktan sonra "hangi kırmızı ? 1. duvardaki kırmızı mı ? 2. duvardaki kırmızı mı?" diye sormak mantıklı değildir. Bu metafordaki duvarlar, nesneler; renk ve boyut ise primitifler gibi düşünülebilir.
Java'da nesne kimlik karşılaştırması "==" operatörü ile yapılır, eşitlik kontrolü ise equals() metodu ile. İki nesne "equal" (eşit) olsa da, aynı değildir. İşte bu farklılık, bu kimlik konusu; JVM seviyesinde primitif tipler ile nesnelerin bellekte farklı şekilde ve bölgelerde tutulmalarına ve farklı şekilde ele alınmalarına neden olur.
Nesne Başlığı (Java'da niye sizeof yok?)
Valhalla tarafından ortaya konan Value Object / Class ve Primitive Object / Class kavramlarının bize ne kazandırdığını anlayabilmek için nesnelerin JVM tarafından bellekte nasıl tutulduğuna göz atmamız gerekiyor.
JVM bir sınıfın nesnesi için bellekte yer ayırdığında, sınıf içindeki alanlar için gereken yere ek olarak, nesne başlığı (object header) denen bir veri yapısı için de yer ayırır. Bu nesne başlığı JVM tarafından bir takım mekanizmalar için ve Java'yı nesneye yönelik (Object Oriented - OO) bir dil yapan polymorfizmi sağlamak ve yabancısı olana sihir gibi görününen reflection (yansıma) özelliğini gerçeklemek için kullanılmaktadır.
Gelin şu nesne başlığı içindeki veriyi biraz daha detaylı inceleyelim. Nesne başlığının ilk kısmı Mark (işaretleme) Word olarak adlandırılır. JVM bu Mark Word kısmını şu amaçlarla kullanır:
- Senkronizasyon / Locking (kilitleme): Java'nın reklamı yapılırken kullanılan "en baştan multi threading için tasarlandı" benzeri iddiaların dayanağı olarak hangi thread'in bu nesneye erişim hakkını rezerve ettiğini yani kilitlediğini depolamak
- Garbage Collection: Java'nın en cazip özelliklerinden biri olan otomatik bellek yönetimini sağlayan Garbage Collector (çöp toplayıcı) tarafından nesnenin kullanılıp kullanılmadığını (ve ilgili detayları) depolamak (bir nevi işaretlemek)
- Nesne Kimliği / Hash: Nesneyi tekil olarak temsil eden hash code (özet kodu diye çevirmeyi deneyelim) yani nesne kimliği bilgisini depolamak
Mark Word'den hemen sonra gelen Class Pointer (Sınıf İşaretçisi) ise nesnenin oluşturulduğu sınıfın bellekteki temsiline işaretçi bilgisidir. "Sınıfın bellekteki temsili" derken; geliştiricinin senktaks seviyesinde yazdığı sınıfı, çalışma zamanında (runtime) sorgulanabilir olarak tutan bir veri yapısından bahsediyoruz. Çalışma zamanında bu veri yapısı kullanılarak; "bu sınıf hangi sınıftan miras almış (extend etmiş) ?", "bu sınıfta hangi metotlar var?", "bu metotların argüman ve dönüş tipleri nelerdir?" gibi birçok kritik soruya dinamik olarak cevap alınır. İşte bu; yani çalışma zamanında bir nesnenin sınıfına, sınıfından da o nesnenin detaylarına dinamik olarak erişebilmek Java platformunun 2 temel özelliğine can verir:
- Inheritance (miras) / polymorphism (çok biçimlilik): Nesneye yönelik programlamanın temellerinden olan; bir nesnenin başka bir nesnenin özelliklerini miras alabilmesi ve aldığı bu özellikleri değiştirebilmesi
- Reflection (yansıma): Daha teorik adıyla Çalışma Zamanı Tip Bilgisi (RTTI - Run-time Type Information) başlı başına bir yazı konusu olmakla birlikte; Hibernate, Lombok vb birçok "sihir" gibi görünen kütüphaneyi mümkün kılan nesne ile ilgili dinamik bilgi erişimi
Nesne başlığının Java'nın popülerliğini sağlayan temel özellikleri için ne kadar hayati olduğu açık ama işte hayat, her şeyin iki yönü var. Java nesnelerinin boyutu içerdikleri alanların (field) toplamından daha fazla ve örneğin C/C++ dillerindeki "sizeof" gibi nesnenin bellekte ne kadar yer kapladığını söyleyen bir operatör yok. Java'nın bellek açısından verimsiz olduğu yönündeki eleştirilerin (kulaktan dolma olmayanlardan bahsediyoruz) merkezinde işte bu nesne başlığı yer alır. Nesne başlığı, oluşturulan her bir nesne için; 64 bit sistemlerde minimum 16 byte ve 32 bit sistemlerde minimum 8 byte yer kaplar. Bunları minimum olarak belirttiğimize dikkat edin çünkü JVM gerçeklemeleri nesne başlığının nasıl ve nerede tutulacağı konusunda tamamen özgür, yeterki yukarıda bahsedilen özellikleri sağlasınlar. Ve geliştiriciler olarak JVM'in tamamen kendi için tuttuğu bu başlığa müdahale etmemiz mümkün değil-di. Ta ki "Project Valhalla'ya" kadar.
Value Object ve Value Class
Nesne başlığının sunduğu imkanlar açık ancak yazılan sınıfların tümünün bahsedilen imkanlara ihtiyaç duyduğunu söylemek zor. Birçok nesne senkronizasyon amacıyla lock (kilit) olarak kullanılmıyor ve çoğu için de nesnenin bellekteki yeri (yani kimliği) değil de eşitliği (yani alanlarının değerlerinin eşitliği) asıl ilgilendiğimiz şey oluyor. Sadece değer tutmak ve bu değerler üzerinde işlem yapmak için yazılan sınıfların tüm sınıflar içinde oldukça büyük bir orana sahip olduğunu söylemek mümkün. İşte Valhalla bu tür sınıflar için bize yeni bir sınıf türü sunuyor: Value Class. Yeni bir keyword’ümüz var, hemen inceleyelim:
// tanımlama
value class Counter {
private int counter;
public Counter(int value) {
checkSubzero(value);
this.counter = value;
}
public int get() { return counter; }
public Counter increment(int delta) {
return new Counter(this.counter + delta);
}
private static void checkSubzero(int value) {
if (value < 0) {
throw new IllegalArgumentException();
}
}
}
// kullanım
Counter counter = new Counter(0);
counter = counter.increment(1);
System.out.println(counter.get());
// eşitlik ve kimlik
assert counter == new Counter(1);
- Kimlik ve Eşitlik: Değer nesneleri kimliksiz nesneler, "==" operatörünün kimlik kontrolü yaptığı hatırlanırsa bu operatör değer nesneleri için "equals()" metodu gibi çalışmalı, iki değer nesnesi (daha doğrusu referansları) "==" ile karşılaştırıldığında alanlarının değerlerinin esit olup olmadığı kontrol edilir, alanları eşit ama birbirinden farklı iki değer nesnesi olamaz
- Immutability (Değişmezlik): Değer sınıfının kendisi ve tüm alanları varsayılan olarak final, yani değer sınıfları extend edilemezler ve abstract olamazlar, buna ek olarak alanlarına bir kez değer atandıktan sonra bir daha değer atanamaz
- Miras alamama (No inhertitance): Bir değer sınıfı ya direkt Object sınıfını extend edebilir veya alanları olmayan (dolayısı ile state sahibi olmayan) soyut (abstract) bir sınıfı extend edebilir, interface konusunda bir kısıt yok
- Senkronizasyonsuzluk: Açıklaması yazmasından daha kolay, değer sınıflarındaki nesne metotları "synchronized" olamaz ve bir değer nesnesi synchronized blok için kilit olarak kullanılamaz
Bu kısıtların neden kaynaklandığını (bazı okuyucularımız için barizdir belki) açıklayıp 1. kısmı az sonra sonlandıracağız ancak ondan önce belirtmekte fayda var: bu kısıtlar dışında değer sınıf ve nesneleri constructor overloading, iç sınıflar, static alanlar, birden fazla interface'den türeme gibi alışılmış sınıf ve nesnelerle aynı özelliklere sahip olabilirler.
Peki ne kazanıyoruz ?
Değer sınıfları ve nesnelerinin yukarıda sayılan kısıtlamalara sahip olması onları bir konuda avantajlı kılıyor, evet tahmin ettiniz: nesne başlığı! Miras ve polymorfizm kısıtlaması ile nesne başlığı içindeki Class Pointer ihtiyacı ortadan kalkıyor ve senkronizasyon için kullanılamamaları da Mark Word ihtiyacını azaltıyor.
Nesne başlığına ek olarak; eşit 2 değer nesnesi arasında kimlik açısından bir fark olmadığı için bunlar bellekte serbestçe kopyalanabilir, alanlarına parçalanıp tekrar bir nesne olarak birleştirilebilir ve program açısından bu hiçbirşey farkettirmez. Bu da JVM'e; değer nesnelerinin bellekte nerede ve ne kadar ek başlıkla tutulabileceği konusunda optimizasyon imkanları sunuyor. Optimizasyon derken: daha az bellek tüketimi ve modern işlemci mimarilerinde daha yüksek performanstan bahsediyoruz. Modern işlemci mimarilerinden bu serinin sonraki kısmında bahsedeceğiz.
Hepsi bu kadar değil
Nesne başlığının olmaması (veya daha küçük boyutta olabilmesi) bellek tüketimi üzerinde olumlu bir etkiye sahip olmakla birlikte Project Valhalla'nın kapısını araladıkları bunla sınırlı değil. Giriş kısmında da belirttiğimiz Primitif Sınıf ve Primitif Nesne konularını 2. yazımızda tartışacağız. Neticede çok uzun yazıları kimse okumaz değil mi ?
Görüşmek üzere.
Yazının 2. kısmına buradan ulaşabilirsiniz.
Yorumlar
Yorum Gönder