"Enter"a basıp içeriğe geçin

JVM (Java Sanal Makinesi) Mimarisi

JVM (Java Virtual Machine), Java uygulamalarını çalıştırmak için çalışma zamanı motoru (runtime engine) görevi yapan soyut bir bilgi işlem makinesidir. Java bayt kodunun yürütülebileceği çalışma zamanı ortamını sağlar. JVM için üç kavram vardır:

  • Specification (Tanımlama): JVM bir tanımlamadır. Bu tanımlama içerisinde Java Sanal Makinesi’nin çalışma mekanizması tarif edilir. Ancak bu tarifin uygulamasını yapacak üretici, algoritma seçimi konusunda serbesttir.
  • Implementation (Uygulama): JVM’in uygulaması JRE (Java Runtime Environment) olarak bilinir. Günümüzde en büyük uygulama sağlayıcısı Java’nın da sahibi olan Oracle’dır (Hotspot VM) ancak diğer şirketler tarafından da bağımsız JVM uygulamaları geliştirilmektedir (örn. GraalVM, Zing JVM, Eclipse OpenJ9, vs).
  • Instance (Örnek): Bir Java sınıfını çalıştırmak için konsola “java” komutu yazılıp entera basıldığında bellekte JVM’in bir örneği oluşturulur.

Genelde Java geliştiricileri, geliştirdikleri yazılımın bayt kodunun JRE (Java Runtime Environment) tarafından yürütüleceğini bilir, ancak birçoğu, JRE’nin bayt kodunu analiz eden, kodu yorumlayan ve yürüten Java Sanal Makinesi’nin (JVM) bir uygulaması olduğunu bilmez. Aslında bir Java geliştiricisi için, JVM mimarisini bilmek daha verimli kod yazmayı sağlaması açısından çok önemlidir.

Bu yazıda, Java’nın JVM mimarisini ve bileşenlerini detaylı olarak ele alacağız.

JVM Nasıl Çalışır?

Sanal Makine, fiziksel bir makinenin yazılımsal uygulaması olarak tanımlanabilir. Java programları da böyle bir sanal makine üzerinde çalışır. JVM, WORA (Write Once Run Anywhere) mantığıyla geliştirilmiştir. Bunun anlamı, bir programcı bir sistemde geliştirdiği Java kodunu, herhangi bir ayarlama yapmadan başka herhangi bir JRE yüklü sistemde çalıştırabilir. Bu JVM sayesinde mümkün olmaktadır. Bir Java kaynak dosyası, Java derleyicisi (javac) tarafından derlendiğinde, bu kaynak dosyasından .class uzantılı çalıştırılabilir bayt kodu (bytecode) dosyası oluşturulur, ardından bu dosya, JVM’ne girdi olarak verilerek belleğe yüklenmesi ve çalıştırılması sağlanır.

JVM Architecture Diagram
JVM Mimari Şeması (kaynak: dzone.com)

Yukarıdaki şemada gösterildiği üzere JVM üç ana alt sisteme bölünmüştür:

  1. ClassLoader Alt Sistemi
  2. Runtime Data Areas (Çalışma Zamanı Veri Alanları)
  3. Execution Engine (Yürütme Motoru)

ClassLoader Alt Sistemi

Classloader, sınıf dosyalarını yüklemek için kullanılan bir JVM alt sistemidir. Java’nın dinamik sınıf yükleme işlevi, ClassLoader alt sistemi tarafından gerçekleştirilir. Bir Java programını her çalıştırdığımızda, bu program belleğe ilk olarak sınıf yükleyici tarafından yüklenir. Sınıf yükleyici aslında üç faaliyetten sorumludur.

  • Loading (Yükleme)
  • Linking (Bağlama)
  • Initialization (Başlatma)

Loading (Yükleme)

Sınıf yükleyicisi .class dosyasını okuyarak, bu dosyadaki bayt kodlarından ikili verileri oluşturur ve metot alanında (method area) saklar. Her bir .class dosyası için JVM, metot alanında aşağıdaki bilgileri depolar.

  • Yüklenen sınıfın tam adı ve bir üst sınıfı.
  • Yüklenen .class dosyası Class mı, Interface mi veya bir Enum mı?
  • Erişim belirleyicileri, değişkenler, metot bilgileri, vs.

Bir .class dosyası belleğe yükledikten sonra JVM, bu dosyayı belleğin heap (yığın) alanında temsil etmek için Class türünde (java.lang paketinde önceden tanımlanmış) bir nesne oluşturur. Bu Class nesnesi, programcı tarafından sınıfın adı, üst sınıf adı, yöntemler ve değişken bilgileri gibi sınıf düzeyindeki bilgileri almak için kullanılabilir. Bu nesne referansını almak için Object sınıfının getClass() yöntemi kullanılabilir.

Not: Yüklenen her .class dosyası için bu sınıfın yalnızca bir nesnesi oluşturulur.

// Bellekteki .class dosyasını temsil etmesi amacıyla JVM tarafından
// oluşturulan Class tipindeki bir nesnenin çalışmasını gösteren bir
// Java programı.
// Gerekli import işlemlerini yapalım.
import java.lang.reflect.Field;
import java.lang.reflect.Method;
// JVM tarafından oluşturulan Class nesnesinin kullanımını 
// göstermek için yazılan Java kodu
public class ClassNesnesiOrnegi {
    public static void main(String[] args)
    {
        Ogrenci o1 = new Ogrenci();
        // Ogrenci sınıfından oluşturulan o1 nesnesinin 
        // JVM tarafından oluşturulan Class nesnesini alalım.
        Class c1 = o1.getClass();
        // c1 nesnesinin getName() metodunu kullanarak o1 nesnesinin 
        // türünü yazdıralım.
        System.out.println(c1.getName());

        // Ogrenci sınıfından oluşturulan o1 nesnesinin tüm metotlarını 
        // dizi halinde alalım.
        Method m[] = c1.getDeclaredMethods();
        for (Method metot : m)
            System.out.println(metot.getName());
 
        // Ogrenci sınıfından oluşturulan o1 nesnesinin tüm değişkenlerini 
        // (field/attribute) dizi halinde alalım.
        Field f[] = c1.getDeclaredFields();
        for (Field field : f)
            System.out.println(field.getName());
    }
}

// Bilgileri yukarıda Class nesnesi kullanılarak getirilen 
// Ogrenci adlı örnek sınıf.
class Ogrenci {
    private String adi;
    private int ogrenciNo;
  
    public String getAdi() { return adi; }
    public void setAdi(String adi) { this.adi = adi; }
    public int getOgrenciNo() { return ogrenciNo; }
    public void setOgrenciNo(int ogrenciNo) { this.ogrenciNo = ogrenciNo; }
}

Bu kodun çıktısı şöyle olacaktır:

Ogrenci
getAdi
setAdi
getOgrenciNo
setOgrenciNo
adi
ogrenciNo

Java’da yerleşik üç sınıf yükleyici vardır. Bu sınıf yükleyicileri, sınıf dosyalarını yüklerken Delegasyon Hiyerarşisi İlkesini takip eder. Bu sınıf yükleyicileri şunlardır:

Bootstrap ClassLoader: Bu sınıf yükleyicisi, Extension sınıf yükleyicinin üst sınıfı olan ilk sınıf yükleyicidir. java.lang paket sınıfları, java.net paket sınıfları, java.util paket sınıfları, java.io paket sınıfları, java.sql paket sınıfları gibi Java Standard Edition’ın tüm sınıf dosyalarını içeren rt.jar dosyasını yükler. Bu sınıf yükleyiciye en yüksek öncelik verilir.

Extension ClassLoader: Bu sınıf yükleyicisi, Bootstrap sınıf yükleyicisinin alt sınıf yükleyicisi ve System sınıf yükleyicisinin üst sınıf yükleyicisidir. $JAVA_HOME/jre/lib/ext dizininde bulunan jar dosyalarını yükler.

System/Application ClassLoader: Bu sınıf yükleyicisi, Extension sınıf yükleyicisinin alt sınıf yükleyicisidir. Varsayılan yol ya da belirtilen sınıf yolundan çalıştırılacak uygulamanın sınıf dosyalarını yükler. Varsayılan olarak, sınıf yolu çalışılan dizine ayarlanmıştır. Komut satırında “-cp” veya “-classpath” anahtarını kullanarak veya “CLASSPATH” ortam değişkenine sınıf yolu verilerek sınıf yolu değiştirilebilir. Bu sınıf yükleyicisi Application (Uygulama) sınıf yükleyicisi olarak da bilinir.

Bu sınıf yükleyicileri Java tarafından sağlanan dahili sınıf yükleyicilerdir. ClassLoader sınıfı extend edilerek amaca özel sınıf yükleyicisi de oluşturulabilir.

// Sınıf yükleyici adını yazdırmak için bir örnek yapalım
public class SinifYukleyiciOrnegi  
{  
    public static void main(String[] args)  
    {
        // Geçerli sınıfın sınıf yükleyici adını yazdıralım.
        // Bu sınıfı, System/Application sınıf yükleyicisi belleğe yükler.
        Class c=SinifYukleyiciOrnegi.class;
        System.out.println(c.getClassLoader());
        // Eğer String sınıfının sınıf yükleyicisinin adını yazdıracak
        // olursak null yazdıracaktır, çünkü String sınıfı rt.jar içindeki  
        // dahili bir sınıftır. Yani Bootstrap sınıf yükleyicisi
        // tarafından yüklenir.
        System.out.println(String.class.getClassLoader());
    }  
} 

Bu kodun çıktısı şöyle olacaktır:

sun.misc.Launcher$AppClassLoader@4e0e2f2a
null

Delegasyon-Hiyerarşisi İlkesi: JVM, sınıfları yüklemek için Delegasyon-Hiyerarşisi ilkesini izler. Bir sınıf yükleme isteği geldiğinde bu isteği öncelikle System/Application sınıf yükleyicisi ele alır. System/Application sınıf yükleyicisi , bu isteği Extension sınıf yükleyicisine, Extension sınıf yükleyicisi de, Bootloader sınıf yükleyicisine iletir. Eğer önyükleme yolunda bir sınıf bulunursa, bu sınıf yüklenir, aksi takdirde, istek tam tersi bir yol izleyerek önce Extension sınıf yükleyicisine ve ardından System/Application sınıf yükleyicisine aktarılır. Son olarak, System/Application sınıf yükleyicisi de sınıfı yükleyemezse, Java.lang.ClassNotFoundException çalışma zamanı istisnasını alırız.

Linking (Bağlama)

Java’da bağlama işlemi C gibi derlenen dillerin aksine çalışma zamanında yapılır. Bu aşamada Verification (doğrulama), preparation (hazırlama) ve (isteğe bağlı olarak) resolution(çözümleme) işlemleri gerçekleştirilir.

Verification (Doğrulama): Bu aşamada .class dosyasının doğruluğu sağlanır. Yani bu dosyanın doğru şekilde biçimlendirilip biçimlendirilmediğini ve geçerli bir derleyici tarafından oluşturulup oluşturulmadığı kontrol edilir. Doğrulama başarısız olursa, çalışma zamanında java.lang.VerifyError istisnasını alırız. Bu işlem, ByteCodeVerifier bileşeni tarafından yapılır. Bu işlem tamamlandıktan sonra sınıf dosyası derleme için hazırdır.

Preparation (Hazırlama): Bu aşamada JVM, tüm sınıf değişkenleri ve statik değişkenler için bellek ayırır ve bunlara varsayılan değerleri atar.

JVM tarafından bu aşamada türlere göre değişkenlere atanan varsayılan değerler şunlardır:

object => null
boolean => false
byte => 0
short => 0
int => 0
long => 0L
float => 0.0f
double => 0.0d
char => '\u0000'

Örneğin aşağıdaki gibi bir değişken tanımlama ve değer atama ifadesini düşünelim:

boolean durum = true;

Bu aşamada kod ve boolean türündeki durum adlı değişken kontrol edilir ve JVM bu değişkene false değerini atar (yukarıda değindiğimiz gibi boolean türünün varsayılan değeri false’dir).

Resolution (Çözümleme): Tüm sembolik bellek referanslarını, Yöntem Alanındaki orijinal referanslarla değiştirme işlemidir. Orijinal referansı bulmak için yöntem alanında arama yapılır.

Initialization (Başlatma)

Bu işlem sınıf yükleme işleminin son aşamasıdır. Bu aşamada tüm sınıf değişkenlerine ve statik değişkenlere, kodda ve (varsa) statik blokta tanımlanmış olan değerleri atanır. Bu işlem sınıf içinde yukarıdan aşağı doğru, sınıf hiyerarşisi içinde de üst sınıftan alt sınıfa doğru yapılır. JVM bir kural olarak, bir sınıfı aktif kullanıma geçmeden önce bu başlatma sürecinden geçirir.

Bir sınıf aşağıdaki durumlardan biri oluştuğunda aktif kullanıma geçmiş olur:

  1. new anahtar kelimesi kullanılarak bu sınıftan nesne oluşturulduğunda (örneğin: Ogrenci o1 = new Ogrenci();)
  2. Bu sınıftan bir statik metot çağırıldığında
  3. Bu sınıftan bir statik alana değer atandığında
  4. Başlangıç ​​sınıfıysa (main() yöntemine sahip olan)
  5. Bu sınıftan yansıma API’leri kullanıldığında (getInstance() metodu)
  6. Bu sınıftan bir alt sınıf başlatıldığında

Bir sınıfı başlatmanın dört yolu mevcuttur. Bunlar,

  1. new anahtar kelimesini kullanarak,
  2. clone() ; metodunu kullanarak,
  3. Yansıma API’sini kullanarak (getInstance();)
  4. IO.ObjectInputStream(); kullanarak.

Runtime Data Areas (Çalışma Zamanı Veri Alanları)

Çalışma Zamanı Veri Alanı (JVM Belleği) aşağıdaki gibi beş ana parçaya ayrılmıştır:

Çalışma Zamanı Bellek Alanı (kaynak: medium.com/nerd-for-tech)

Method Area (Metot Alanı) – Kodun çalışması boyunca statik değişkenler dahil tüm sınıf düzeyindeki veriler burada depolanır. Statik değişkenler, statik metotlar, statik bloklar, nesne örneğine ait metotlar, sınıf adı ve (varsa) bir üst sınıf adı bilgilerini tutar. Her JVM için yalnızca bir metot alanı vardır ve paylaşılan bir kaynaktır.

Heap Area (Yığın Alanı) – Nesnelere ayrılmış çalışma zamanı veri alanıdır. Tüm nesneler ve bunlara karşılık gelen örnek değişkenleri ve dizileri burada saklanır. Her JVM için ayrı bir Heap Alanı vardır.

Örnek olarak aşağıdaki kod satırını ele alalım,

Ogrenci o1 = new Ogrenci();

Burada, Ogrenci sınıfından new anahtar kelimesi ile oluşturduğumuz o1 nesnesi Heap Alanına yüklenir.

Metot ve Heap alanları belleği birden çok iş parçacığı için paylaştığından burada bulunan veriler thread-safe değildir.

Stack Area (Yığın Alanı) – JVM her iş parçacığı için, burada depolanan bir çalışma zamanı yığını oluşturur. Stack alanı, Stack Frame veya Activation Record olarak adlandırılan bloklardan oluşur ve metot çağrılarının yerel değişkenlerini burada tutar. Her metot çağrısı için, stack çerçevesine bir giriş yapılır. Tüm yerel değişkenler bu stack belleğinde oluşturulur. Metot çağrısı tamamlandığında bu çerçeve stack alanından kaldırılır (POP). Sonuçta bu bir yığın olduğundan, Son Giren İlk Çıkar yapısını kullanır. Stack alanı, paylaşılan bir kaynak olmadığı için iş parçacıkları için güvenlidir (thread-safe).

Bir Stack Frame üç alt kısımdan oluşur:

Local Variable Array (Yerel Değişkenler Dizisi) – Java stack çerçevesinin yerel değişkenler bölümü, bir yöntemin parametrelerini ve yerel değişkenlerini içerir. Bu bölüm, sıfır tabanlı bir dizi olarak düzenlenmiştir. Bu dizideki bir değeri kullanan tüm komutlar, sıfır tabanlı bu diziye bir indisle erişir (örn. iload_0, iload_1, vs.). Derleyiciler, parametreleri bildirildikleri sıraya göre önce yerel değişken dizisine yerleştirir. Int, float, referans ve geri dönüş adresi türündeki değerler, yerel değişkenler dizisinde bir girdilik yer işgal eder. Byte, short ve char türündeki değerler, yerel değişkenler dizisine depolanmadan önce int türüne dönüştürülür. Long ve double türündeki değerler dizide iki ardışık girdi alanını işgal ederler. Komutlar, bu dizide bir long veya double değere referans vermek için, bu değer tarafından işgal edilen iki ardışık girdiden ilkinin indisini verir. Örneğin bir long değer, yerel değişkenler dizisinde üç ve dördüncü indisleri işgal ediyorsa, bu değere erişecek komut üçüncü indisten referans vermelidir.

Operand Stack (İşlenen Yığını) – Yerel değişken dizisine benzer şekilde işlenen yığını da bir dizi olarak düzenlenir. Ancak dizi indislerinin aksine, işlenen yığınındaki değerlere push ve pop operasyonları ile erişilir. JVM, yerel değişkenlerde olduğu gibi işlenen yığınında da aynı veri türlerini saklar. Bunlar: int, long, float, double, referans, ve geri dönüş tipi türleridir. Byte, short ve char türündeki değerleri işlenen yığınına göndermeden önce int‘e dönüştürür. JVM, operand stack alanını ara işlemleri gerçekleştirmek için runtime alanı olarak kullanır.

Örneğin: JVM’in, iki int içeren iki yerel değişkeni birbirinden çıkaracak ve int sonucunu üçüncü bir yerel değişkende saklayacak bir kodu nasıl kullanacağını aşağıdaki örnekle görelim:

iload_0    // Yerel değişken dizisinin 0. indisindeki int değeri operand yığınına push et.
iload_1    // Yerel değişken dizisinin 1. indisindeki int değeri operand yığınına push et.
isub       // Operand yığın alanından iki int değeri pop et, bunları birbirinden çıkar ve sonucu operand yığınına push et.
istore_2   // Sonucu operand yığın alanından pop et ve yerel değişken dizisinin 2. indisine kaydet. 

Burada ilk iki talimat iload_0 ve iload_1, yerel değişken dizisindeki değerleri operand yığınına push edecektir. Ardından isub komutu bu iki değeri birbirinden çıkaracak ve sonucu operand yığınına geri depolayacaktır. istore_2 komutundan sonra sonuç operand yığınından çıkacak ve yerel değişken dizisinin 2. konumuna depolanacaktır.

Working of LVA and OS
Operand Yığının çalışma örneği (kaynak: geeksforgeeks.org)

Frame Data (Çerçeve Verisi) – Java yığın çerçevesinin çerçeve verileri bölümü bir metoda ait tüm sembolik referanslara, normal metot geri dönüşüne ve istisna gönderimine (exception dispatching) ait verileri içerir. Java sanal makinesinin komut setindeki birçok komut, sabit havuzundaki (constant pool) girişlere referans verir. Java sanal makinesi, sabit havuzundaki bir girdiye referans veren herhangi bir komutla karşılaştığında, bu bilgilere erişmek için çerçeve verilerinin sabit havuzundaki girdiyi gösteren işaretçisini kullanır. Ayrıca çerçeve verileri alanında, yöntemin yürütülmesi sırasında ortaya çıkan istisnaları işlemek için sanal makinenin kullandığı metodun istisna tablosuna referans da bulunur. Bunlara ek olarak, yığın çerçevesi, hata ayıklamayı verileri gibi uygulamaya bağlı diğer bilgileri de içerebilir.

PC (Program Counter) Yazmaçları – Her iş parçacığının o an çalışan komutun adresini tutan kendine ait bir PC yazmacı vardır. Her komut çalıştırıldığında, PC yazmacı bir sonraki komutun adresiyle güncellenir. Herhangi bir anda, her Java Sanal Makinesi iş parçacığı, tek bir metodun, yani o iş parçacığı için geçerli metodun kodunu yürütür. Eğer bu metot bir native metot (Java programlama dili dışında bir dilde yazılmış bir metot) değilse, PC yazmacı, yürütülmekte olan Java Sanal Makinesi komutunun adresini içerir. İş parçacığı tarafından yürütülmekte olan metot bir native metot ise, Java Sanal Makinesinin PC yazmacının değeri null veya undefined olacaktır. Java Sanal Makinesi’nin PC yazmacı, herhangi bir platformdaki bir metot geri dönüş adresini veya yerel bir işaretçiyi tutacak kadar geniştir.

Native Method Stack (Yerel Metot Yığını) – Bu yığın alanında, C/C++ gibi Java dışında bir dilde yazılmış olan yerel metotlar ile ilgili veriler tutulur. Bir JVM implementasyonu, yerel metotların kullanımı için “C yığınları” olarak bilinen geleneksel yığınları kullanabilir. Yerel metot yığını, Java Sanal Makinesi’nin C gibi bir dildeki komut seti için bir yorumlayıcı uygulamasıyla da kullanılabilir. Yerel metotları yükleyemeyen ve kendileri geleneksel yığınlara dayanmayan JVM uygulamalarının yerel metot yığınlarına sahip olması gerekmez. Eğer bir JVM uygulamasının yerel metot yığını varsa, oluşturulan her bir iş parçacığı için ayrı bir yerel metot yığını oluşturulur.

Örneğin yerel metot alanının kullanımı ile ilgili aşağıdaki örnek senaryoya bakalım.

Thread 1 (T1) için aşağıdaki örnek kodu inceleyelim.

M1(){
   M2();
}
-----------------
M2(){
   M3();
}

Burada M1 metodu çağrıldığında, T1 iş parçacığında ilk çerçeve oluşturulacak ve oradan M2 metoduna gidecek ve o sırada ikinci çerçeve oluşturulacak ve oradan yukarıdaki örnek kodda görüldüğü gibi M3 metoduna gidecek. Böylece M2 altında yeni bir çerçeve oluşturulacaktır. Her bir metottan çıktığında, yığın çerçeveleri sırasıyla yok edilecektir.

Ancak yığındaki T4 iş parçacığında, M2 metodu yerel bir metoda erişiyor. Bundan dolayı, PC yazmacındaki T4 null veya undefined olacak, ancak yukarıdaki şemada gösterildiği gibi diğer tüm üç iş parçacığı (T1, T2, T3) hakkındaki bilgileri tutmaya devam edecektir.

Execution Engine (Yürütme Motoru)

Çalışma Zamanı Veri Alanına yüklenen bayt kodu (.class), Yürütme Motoru tarafından okunur ve satır satır çalıştırılır. Temel olarak, Yürütme Motorunun Java sınıflarını yürütmek için üç ana bileşeni vardır:

Interpreter (Yorumlayıcı) – Yorumlayıcı, bayt kodunun makine koduna dönüştürülmesinden sorumludur. Yorumlayıcı bayt kodunu hızlı yorumlar ancak yürütme işlemi satır satır gerçekleştiği için yavaştır. Yorumlayıcının dezavantajı, bir metot birden çok kez çağrıldığında, her seferinde yeni bir yorumlamanın gerekli olmasıdır ve bu da sistemin performansını düşürür. JIT derleyicisinin Yorumlayıcıya paralel çalışmasının nedeni bu performans darboğazını ortadan kaldırmaktır.

JIT Derleyicisi – JIT Derleyicisi, yorumlayıcının hız kaynaklı dezavantajını ortadan kaldırır. Yürütme Motoru, bayt kodunu dönüştürme işleminde yorumlayıcıyı kullanır, ancak tekrarlanan kod bulduğunda, JIT derleyicisini kullanarak tüm bayt kodunu derler ve yerel koda (makine kodu) dönüştürür. Bu yerel kodlar önbellekte saklanır. Tekrarlanan yöntem her çağrıldığında, bu yerel kod verilir. Yerel kodla yürütme, komutu yorumlamaktan daha hızlı olduğu için performans da artar.

JIT derleyicisi dört bileşenden oluşur. Bunlar:

  1. Ara Kod Üreticisi – Ara kod üretir
  2. Code Optimizer – Yukarıda oluşturulan ara kodu optimize etmekten sorumludur
  3. Hedef Kod Oluşturucu – Makine Kodu veya Yerel Kod oluşturmaktan sorumludur
  4. Profiler – Bir yöntemin birden çok kez çağrılıp çağrılmadığı gibi önemli noktaları bulmaktan sorumlu olan özel bir bileşendir.

Burada “yerel makine kodu” olarak bahsettiğimiz şey doğrudan işletim sistemi tarafından anlaşılabilen yürütülebilir koddur. Eğer uygulama bir Windows JVM tarafından çalıştırılıyorsa, Windows işletim sistemi tarafından yerel olarak anlaşılabilen kod üreteceği, Eğer Linux’ta çalışan bir JVM tarafından çalıştırılıyorsa, Linux tarafından yerel olarak anlaşılabilen kod üreteceği anlamına gelir. Örneğin, Windows JVM yerel bir Windows kodu oluşturabilirken, Linux JVM yerel bir Linux kodu oluşturabilir. Bazen geliştiriciler bu yerel kod kombinasyon sürecini bilmeleri gerekmediğini düşünürler, ancak bunun önemli bir anlamı vardır.

Bir Java uygulaması ne kadar uzun süre çalıştırılırsa o kadar hızlı çalışır.

Bunun nedeni, JVM’nin kodun profilini (profiler tarafından) çıkarabilmesi ve yerel makine koduna derleyerek hangi bitlerinin optimize edilebileceğini bulabilmesi için yeterli zamana sahip olmasıdır. Bu nedenle, her dakika birden çok kez çalışan bir yöntem yüksek ihtimalle hızlı bir şekilde JIT derlenir, ancak günde bir kez çalışan bir yöntem hiçbir zaman JIT derlenmeyebilir.

Önemli Not: Bayt kodunu yerel makine koduna derleme işlemi ayrı bir iş parçacığında çalışır. Elbette JVM’nin kendisi çok iş parçacıklı bir uygulamadır. Bu nedenle, JVM içindeki, bayt kodunu yorumlayan kodu çalıştırmaktan ve bayt kodunu yürütmekten sorumlu, iş parçacıkları, JIT derlemesi yapan iş parçacığından etkilenmeyecektir. Bu, JIT derleme işleminin uygulamanın çalışmasını durdurmadığı anlamına gelir.

Garbage Collector – Heap alanında referanssız nesneler olup olmadığını kontrol eder ve hafızayı geri kazanmak için bu nesneleri yok eder, böylece yeni nesneler için yer açar. Garbage Collector arka planda çalışır ve Java belleğinin verimli çalışmasını sağlar. Çöp Toplama işlemi, System.gc() çağrılarak da tetiklenebilir, ancak yürütme garanti edilmez. Bu süreçte yer alan iki aşama vardır:

  1. Mark (İşaretle) — Garbage Collector bu aşamada, heap alanındaki kullanılmayan nesneleri tanımlar/işaretler.
  2. Sweep (Temizle) — Garbage Collector bu aşamada işaretlenmiş nesneleri kaldırır.

Java Native Interface (Java Yerel Arabirimi): JNI, Native (Java dışı) metot kütüphaneleri (C/C++) ile etkileşim kurmak için kullanılır. JNI, JVM’nin Java’daki performans kısıtlamalarının üstesinden gelmek için bu kütüphaneleri çağırmasını sağlar.

Native Method Libraries (Yerel Metot Kütüphaneleri): Bu kütüphaneler, Yürütme Motorunun ihtiyacı olan C ve C++ gibi diğer programlama (java dışı) dillerinde yazılmış kütüphanelerdir. Bu kütüphanelere JNI yoluyla erişilebilir ve çoğunlukla .dll veya .so dosya uzantısı biçimindedir.

Kaynaklar

İlk Yorumu Siz Yapın

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir