Press "Enter" to skip to content

JVM – İçeride Neler Oluyor?

Last updated on October 18, 2011

Java ile uğraşan hemen herkes JVM (Java Virtual Machine) hakkında az çok bilgiye sahiptir. Basit olarak söylemek gerekirse JVM, yazdığımız java uygulamalarını çalıştıran sanal bir makinedir. Peki bu sanal makinenin içerisinde arka planda neler döndüğünü hiç merak ettiniz mi? Bu yazıda programcıların yazdığı .java uzantılı bir kod dosyasının derlendikten sonra hangi aşamalardan geçerek çalıştırıldığını anlatmaya çalışacağım. JVM ile ilgili daha basit düzeyde detaylı bilgi almak istiyorsanız bu adresteki yazımı okuyabilirsiniz.

Bir java programcısının yazdığı .java uzantılı dosya, Java derleyicisi tarafından derlenerek çalıştırılmaya hazır .class uzantılı bir “bytecode” dosyasına dönüştürülür. Bu aşamadan sonra programın çalıştırılması işini JVM yapar. JVM bir Java programını çalıştırmadan önce “yükleme”, “bağlama” ve “ilklendirme” olmak üzere 3 aşamadan geçirir. Şimdi sırasıyla bunları inceleyelim.

Yükleme (Loading)

Bu aşamada yapılanları anlayabilmek için öncelikle metaclass kavramından bahsetmek gerekiyor. Java’da yazdığımız bütün sınıflar ve arayüzler aslında java.lang paketinin altında bulunan Class sınıfının bir örneğidir. Bir başka deyişle yazdığımız sınıflar da aslında Class sınıfının bir nesnesidir. Java’da Reflection ile birlikte sıkça kullanılan Class sınıfı, yazdığımız sınıflarla ilgili bütün bilgileri (metotlar, değişkenler, sınıfın hangi paket içerisinde olduğu, hangi arayüzleri gerçekleştirdiği vs.) çalışma zamanında (runtime) öğrenmemizi sağlar. Bu şekilde sınıfları temsil eden sınıflara metaclass denmektedir. JVM bir sınıfı çalıştırmadan önce o sınıfı bir Class nesnesine dönüştürmektedir.

JVM’nin bir sınıfı çalıştırabilmesi için o sınıfın ikili formunun (.class uzantılı dosya) öncelikle JVM’ye yüklenmiş olması gerekmektedir. Yükleme işlemi JVM içerisinde önceden tanımlanmış sınıf yükleyicileri ile veya bizim sonradan yazacağımız sınıf yükleyicileri ile yapılabilir. Sınıf yükleyici belirli bir klasör altında çalıştırılacak sınıf ismiyle eşleşen .class uzantılı bir dosya arar. Bulduğu zaman da bu dosyayı bir Class nesnesine dönüştürerek JVM’ye yüklenmesini sağlar. Derlenmiş dosya bulunamazsa bir exception durumu ortaya çıkar ve işlem durdurulur.

JVM içerisinde tanımlı Bootstrap Class Loader, Extensions Class Loader ve System Class Loader gibi sınıf yükleyicileri vardır. Bu yükleyiciler belli klasörler altında bulunan sınıfları JVM’ye yüklerler. Bunların dışında bizler de ClassLoader sınıfını kalıtarak kendi sınıf yükleyicilerimizi yazabilir ve sınıf yükleme işlemini özelleştirebiliriz. Böylece yüklenen sınıfların önbelleğe alınarak sonradan yapılacak isteklere daha çabuk cevap verilmesi veya birbiriyle ilişkili olduğu düşünülen sınıfların bir anda yüklenerek ek yüklemelerden kaçınılması gibi optimizasyonlar söz konusu olabilir. Hatta üst düzey güvenlik gerektiren uygulamalarda bytecode’u şifreleyerek JVM’ye yüklemek de mümkün olabilir.

Bağlama (Linking)

Başarılı bir şekilde JVM’ye yüklenen sınıflar için bir sonraki aşama bağlamadır. Bağlama işlemi kısaca bir sınıf veya arayüzün derlenmiş halinin JVM tarafından çalıştırılabilir hale getirilmesidir. Bağlama kendi içinde Doğrulama, Hazırlık ve Çözme olmak üzere 3 aşamadan oluşur.

Doğrulama (Verification): Doğrulama işlemi bir sınıf veya arayüzün ikili formunun yapısal olarak doğruluğunun kontrol edildiği aşamadır. Her komuta ait bir komut kodunun (opcode) olup olmadığı, jump komutlarının atlama yaptığı yerin geçerli bir yer olup olmadığı gibi kontroller bu aşamada yapılır.

Bu aşama aynı zamanda Java programlarının güvenlik denetimlerinin yapıldığı en önemli aşamalardan birisidir. Sisteme zarar verebilecek kod parçalarının denetimi de bu aşamada yapılır.

Hazırlık (Preparation): Hazırlık aşamasında, sınıf ve arayüzlere ait static alanlar yaratılıp bu alanlara ön tanımlı değerler atanır. Daha önemlisi sınıflara ait “method table” denilen veri yapıları bu aşamada oluşturularak ileride yapılacak metot çağrılarında hız artışı sağlanır. Bu tabloyu oluşturmak ileride sınıflar ve arayüzler üzerinde yapılacak işlemlerde verimlilik ve hız artışı sağlayacak işlemlerden sadece birisidir. Farklı JVM gerçekleştirimleri (implementation) ihtiyaca göre bu aşamada ön hesaplamalar yaparak işlemleri hızlandırıcı birçok başka optimizasyon sağlayabilir.

Çözme (Resolution): Bunu tam olarak nasıl çevireceğimi ben de bilemedim ama bulabildiğim en mantıklı kelime “çözme” oldu. Çözme aşaması, JVM’nin yüklediği sınıfta yer alan diğer sınıflara yapılan referansların belirlenerek, referans edilen sınıfların da JVM’ye yüklenmesi, bağlanması ve başlangıç durumuna getirilmesi işlemlerinin yapıldığı aşamadır. JVM başka bir sınıfa ait referans içeren bir komutu işletecekse, bu sınıfın da önceki sınıfların geçtiği bütün işlemlerden geçmesi gereklidir.

Çözme işleminin bağlama aşamasında yapılması isteğe bağlıdır. Farklı JVM gerçekleştirimleri bunu farklı şekillerde ele alabilir. Bir JVM ilk sınıf yüklendiğinde bu sınıfta yer alan diğer bütün referansların ilgili olduğu sınıfları da yükleyip bağlayabilir. Tabi sonradan yüklenen sınıflarda da başka sınıflara referans olabileceği için bu özyineli olarak bir süre devam edebilir. Farklı bir JVM ise sadece ilk sınıfı başta yükleyip, daha sonra referansı içeren komut işletilirken ilgili sınıfı yükleme, bağlama ve ilklendirme işlemlerinden geçirebilir. Buna “lazy resolution” denir. İlk senaryoda bütün sınıflar başta yüklendiği için bulunamayan bir sınıf sonucu hata en başta alınırken ikinci senaryoda bu hata, ilgili sınıfın işletimine geçilecekken alınır. Ayrıca bir sınıfa ait referans dosyada varsa ancak kullanılmıyorsa, ilk senaryoya göre bu sınıf yüklenirken ikinci senaryoda yüklenmeyebilir.

İlklendirme (Initialization)

Bu aşamada bir sınıf için statik kod blokları ve statik alanlar için ilk değer atamalarını içeren satırlar işletilir. Yani sınıf kullanılmaya başlamadan önce yapılması gereken ilk değer atamaları yapılır ve işletilmesi gereken satırlar işletilir. Bir sınıf için bu işlem yapılmadan önce mutlaka ata sınıfının (superclass) ilklendirilmesi gerekmektedir.

Aşağıdaki 3 durumdan biri gerçekleşmeden hemen önce o sınıf için ilklendirme işlemi yapılır:

  1. Sınıfa ait bir nesne oluşturmak
  2. Sınıfa ait static bir metotun çağrılması
  3. Sınıfa ait sabit olmayan bir static alanın (field) kullanılması veya bu alana bir değer atanması (sabit alanlar zaten derleme zamanında ele alınırlar)

Yeni çalıştırdığımız bir programın main metodu da static olacağı için (2. durum) ilklendirme işlemi main metodunu içeren sınıf için hemen yapılacaktır.

Ve nihayet  bu kadar işlemden sonra JVM, programımızın main metodunu çağıracak ve işletim başlayacaktır. Bundan sonra JVM, yüklenip bağlanan ve ilklendirilen sınıfa ait .class dosyasındaki komutları tek tek ele alarak işletecektir.

Sizin de tahmin edeceğiniz üzere burada anlatılan aşamalar aslında çok daha karmaşık. Sadece yükleme kısmı bile sayfalarca yazılabilir. Araştırma yaparken yükleme aşaması üzerine yazılmış makalelere bile rastladım. Bütün detaylarını bilmesek de JVM’nin kabaca neler yapıp bizim yazdığımız programları çalıştırabildiğini anlamak, hem karşılaştığımız sorunları çözmede bize yardımcı olacak hem de daha rahat kod yazmamızı sağlayacaktır.

Share

2 Comments

  1. Selman
    Selman March 4, 2013

    JVM’nın detaylarına, derinlerine giriş için faydalı yazı olmuş, teşekkürler dostum.

  2. yücel gemici
    yücel gemici April 6, 2015

    başarılı bir çalışma olmuş.ellerine emeğine sağlık.

Leave a Reply

%d bloggers like this: