Bu yazımda bir Java nesnesinin hafıza alanında (heap) nasıl yer aldığını yanı sahip olduğu hafiza düzenini (object layout) aktarmak istiyorum. Bu amaçla aşağıda yer alan sınıfı kullanacağım.
public class BirSinif { boolean b; char c; int i; float f; double d; String s; byte bb; }
Java nesnelerinin hafıza düzenini elde etmek için JOL isimli aracı kullandım. Burada JOL kullanım örneklerini bulabilirsiniz.
Yukarıda yer alan BirSinif isimli sınıfın hafıza düzenini JOL aracılığı ile şu şekilde edinebiliriz:
public static void main(String[] args) throws Exception { System.out.println(ClassLayout.parseClass(BirSinif.class).toPrintable()); }
Ekran çıktısı şu şekilde olacaktır:
com.kurumsaljava.jvm.jol.JOLSample1$BirSinif object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int BirSinif.i N/A 16 8 double BirSinif.d N/A 24 4 float BirSinif.f N/A 28 2 char BirSinif.c N/A 30 1 boolean BirSinif.b N/A 31 1 byte BirSinif.bb N/A 32 4 String BirSinif.s N/A 36 4 JOLSample1 BirSinif.this$0 N/A Instance size: 40 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
BirSinif sınıfından bir nesne oluşturulduktan ve hafızada konuşlandırıldıktan sonra bu nesne arka arkaya dizilmiş byte elementlerinden oluşan bir düzene sahip olacaktır. Bir Java nesnesinin hafızada kapladığı alan hep sekizin katlarıdır.
Nesnenin kapladığı hafıza alanının ilk 12 byte elementi nesne başlığını (header) teşkil etmektedir. Başlık bünyesinde mark word ve klass pointer ismini taşıyan yapılar yer almaktadır. Klass pointer 32 bitlik sistemlerde 4 byte uzunluğundadır ve nesnenin heap hafıza alan adresini ihtiva etmektedir. 64 bitlik sistemlerde bu alan 8 byte uzunluktadır. 64 bitlik sistemlerde nesne hafıza alanı adreslerinin 4 byte artış göstermesi CPU belleklerinde (cache) daha az sayıda nesne referansi tutulabilmesine sebebiyet vermiştir. Bu aynı anda daha az sayıda nesne üzerinde işlem yapıldığı anlamına gelmektedir. Bu sorunu gidermek için compressed oops (ordinary object pointers) mekanizması kullanılmaktadır. 64 bitlik bir sistemde tüm hafıza alanı 2^64 genişliğindedir. Bu genişlikteki alanı sadece 64 bit ile adreslemek mümkündür. Bu yüzden klass pointer 8 byte uzunlulğunda olmak zorundadır. 64 bitlik sistemlerde compressed oops mekanizması kullanıldığında, klass pointer 4 byte uzunluğu ile yetinmekte ve bu değer adres offset değeri olarak kullanılarak, yine teoride 64 bitlik adres alanını adreslemek için kullanılabilmektedir. -XX:+UseCompressedOops JVM parametresi ile compressed oops mekanizması yönetilebilmektedir.
Nesne başlığının diğer parçası olan mark word 32 bit sistemlerde 4, 64 bit sistemlerde 8 byte uzunluğundadır ve nesne lock, nesnenin yaşı, hash değeri, hangi thread tarafından bloke edildiği ve garbage collection işlemleri için kullanılmaktadır. Markoop.hpp dosyasında bu yapının tanımlaması yer almaktadır. Aşağıdaki alıntıda 32 bit ve 64 bit sistemlerde mark word yapısının nasıl şekillendirildiği yer almaktadır.
// 32 bits: // -------- // hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object) // JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) // size:32 ------------------------------------------>| (CMS free block) // PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object) // // 64 bits: // -------- // unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object) // JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object) // PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object) // size:64 ----------------------------------------------------->| (CMS free block) // // unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object) // JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object) // narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object) // unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
Nesne başlığının ardından sınıf bünyesinde kullanılan sınıf değişkenleri için rezerve edilen byte elementleri gelmektedir.
BirSinif bünyesinde birçok veri tipine sahip değişkenler yer almaktadır. Bu değişkenlerin kapladıkları hafıza alanlarını aşağıda görmekteyiz.
12 4 int BirSinif.i N/A 16 8 double BirSinif.d N/A 24 4 float BirSinif.f N/A 28 2 char BirSinif.c N/A 30 1 boolean BirSinif.b N/A 31 1 byte BirSinif.bb N/A 32 4 String BirSinif.s N/A 36 4 JOLSample1 BirSinif.this$0 N/A
Int görüldüğü gibi 4 byte genişliktedir ve 32 bit değerleri ihtiva edebilmektedir. Buna karşın double 8, float 4, char 2, boolean 1, byte 1 ve String 4 byte genişliktedir. Hepsini topladığımızda BirSinif sınıfından olan bir nesnenin hafızada kapladığı alan 40 byte büyüklüktedir.
Bu yazımda Java nesnelerinin hafızadaki düzenlerine değindim. “JVM Nasıl Çalışır” başlıklı yeni yazı serisine JVM bünyesinde olup, bitenlere ışık tutmak için yeni blog yazılarımı yakında sizlerle paylaşacağım.
EOF (End Of Fun)
Özcan Acar