← Kursa Dön
📄 Text · 15 min

JVM, JDK ve JRE: Kim Neyin Nesi?

Java dünyasına adım attığında karşına üç kısaltma çıkar: JVM, JRE, JDK. Bu üçü birbirine o kadar bağlı ki, yeni başlayanlar genellikle hangisinin ne olduğunu karıştırır. Ama merak etme — bu dersin sonunda bu kavramlar kafanda netleşmiş olacak.

Şimdi bu üç kavramı teker teker ele alacağız. Ama önce büyük resmi görelim.


Büyük Resim: Matruşka Bebek Analojisi

Bu üç kavramı anlamanın en iyi yolu Matruşka bebek (iç içe geçen Rus bebekleri) analojisidir.

En dıştaki büyük bebek JDK — her şeyi içerir. Onun içinde JRE — programları çalıştırmak için gereken her şey var. Ve en içte JVM — Java kodunu gerçekten çalıştıran motor.

┌─────────────────────────────────────────┐
│  JDK (Java Development Kit)             │
│  ┌───────────────────────────────────┐  │
│  │  JRE (Java Runtime Environment)   │  │
│  │  ┌─────────────────────────────┐  │  │
│  │  │  JVM (Java Virtual Machine) │  │  │
│  │  └─────────────────────────────┘  │  │
│  │  + Standart Kütüphaneler          │  │
│  └───────────────────────────────────┘  │
│  + javac (Derleyici)                    │
│  + Geliştirme Araçları                  │
└─────────────────────────────────────────┘

Eğer sadece Java programlarını çalıştırmak istiyorsan → JRE yeterli. Eğer Java programlarını yazmak ve derlemek istiyorsan → JDK lazım.

Biz geliştirici olacağımız için her zaman JDK kuracağız.


JVM: Java Virtual Machine

JVM, Java'nın kalbindeki motor. Adı "sanal makine" ama fiziksel bir makine değil — işletim sisteminin üzerinde çalışan bir yazılım.

JVM Ne Yapar?

JVM'in tek bir görevi var: bytecode'u alıp çalıştırmak.

Sen Java kodu yazarsın. javac derleyicisi bu kodu bytecode formatına çevirir (.class dosyaları). Sonra JVM bu bytecode'u alır ve senin işletim sisteminin anlayacağı makine koduna çevirir.

Senin Kodun (.java)
       ↓
    javac (derleyici)
       ↓
  Bytecode (.class)
       ↓
      JVM
       ↓
  Makine Kodu (CPU'nun anladığı dil)

Bytecode Nedir?

Bytecode, Java'nın "evrensel dili"dir. Ne Windows'un, ne Mac'in, ne Linux'un doğal dili değildir. JVM'in anladığı özel bir format.

Bir .class dosyasını metin editörüyle açarsan anlamlı bir şey göremezsin — binary veri görürsün. Ama javap komutuyla bytecode'u okunabilir hale getirebilirsin:

// Kaynak kod
public class Toplama {
    public static void main(String[] args) {
        int a = 5;
        int b = 10;
        int sonuc = a + b;
        System.out.println(sonuc);
    }
}

Bu kodun bytecode karşılığı (basitleştirilmiş):

iconst_5          // 5 sayısını yığına koy
istore_1          // değişken a'ya ata
bipush 10         // 10 sayısını yığına koy
istore_2          // değişken b'ye ata
iload_1           // a'yı yığına yükle
iload_2           // b'yi yığına yükle
iadd              // topla
istore_3          // sonuca ata
getstatic ...     // System.out'u al
iload_3           // sonucu yükle
invokevirtual ... // println'i çağır

Bytecode'u ezberlemenize gerek yok. Ama şunu bil: JVM bu komutları tek tek okuyup işletim sistemine uygun makine koduna çeviriyor.

Platform Bağımsızlık Nasıl Çalışıyor?

İşte sihir burada:

  • Bytecode her yerde aynı — değişmez.

  • JVM her platforma özel yazılmış — Windows için ayrı JVM, Mac için ayrı JVM, Linux için ayrı JVM var.

Yani sen kodu bir kere yazıp bytecode'a çeviriyorsun. Sonra bu bytecode'u istediğin platforma götürüyorsun, orada kurulu JVM çalıştırıyor. Senin hiçbir şeyi değiştirmene gerek yok.

            [Bytecode]
           /    |     \
          /     |      \
   Windows    Linux    Mac
     JVM       JVM     JVM
      ↓         ↓       ↓
   Windows    Linux    Mac
   Makine     Makine   Makine
   Kodu       Kodu     Kodu

💡 İpucu: "Java platform bağımsızdır" derken kastedilen şu: bytecode platform bağımsızdır. JVM'in kendisi platforma bağımlıdır — her işletim sistemi için ayrı bir JVM derlenmiştir. Ama bu senin sorunun değil, JVM geliştiricileri bunu hallediyor.


JIT Compiler: JVM'in Gizli Silahı

JVM başlangıçta bytecode'u satır satır yorumluyordu (interpreter). Bu yavaştı. Sonra JIT (Just-In-Time) Compiler geldi ve oyunu değiştirdi.

JIT Compiler şöyle çalışır:

  1. JVM programı çalıştırmaya başlar, ilk başta bytecode'u yorumlar (interpreter).

  2. Hangi kod parçalarının sık çalıştığını takip eder (hot spots = sıcak noktalar).

  3. Sık çalışan kod parçalarını doğrudan makine koduna derler ve önbelleğe alır.

  4. Bir sonraki sefer o kod çağrıldığında, yorumlamak yerine önceden derlenmiş makine kodunu kullanır.

Bu yüzden Java programları başlangıçta biraz yavaş olabilir ama zamanla hızlanır. Uzun süre çalışan sunucu uygulamalarında Java, C++'a yakın performans gösterebilir.

⚠️ Dikkat: "Java yavaştır" efsanesi 1990'lardan kalma. Modern JVM'ler JIT derlemesi, escape analysis, inline optimization gibi tekniklerle son derece hızlıdır. Netflix, Twitter, LinkedIn gibi devler boşuna Java kullanmıyor.


JVM'in Diğer Görevleri

JVM sadece bytecode çalıştırmaz. Birçok kritik görevi daha var:

Bellek Yönetimi (Garbage Collection)

C/C++'ta belleği sen yönetirsin — malloc ile ayırırsın, free ile serbest bırakırsın. Unutursan bellek sızıntısı (memory leak) olur.

Java'da bunu JVM'deki Garbage Collector (GC) yapar. Kullanılmayan nesneleri tespit eder ve bellekten temizler. Senin hiçbir şey yapmana gerek yok.

public class BellekOrnegi {
    public static void main(String[] args) {
        // Nesne oluşturuluyor, bellek ayrılıyor
        String mesaj = new String("Merhaba");

        // mesaj artık başka bir şeyi referans ediyor
        mesaj = new String("Güle güle");

        // "Merhaba" stringi artık hiçbir yerden erişilemez
        // Garbage Collector bunu bir süre sonra temizleyecek
    }
}

Güvenlik (Security Manager)

JVM, çalışan programın ne yapabileceğini kontrol edebilir. Dosya sisteme erişimi, ağ bağlantılarını, sistem kaynaklarını kısıtlayabilir. Bu özellikle applet'ler ve sandbox ortamları için kritiktir.

Sınıf Yükleme (Class Loading)

JVM, sınıfları lazily (tembel) yükler. Yani bir sınıfa ilk kez ihtiyaç duyulduğunda diskten okuyup belleğe alır. Bu, büyük uygulamaların başlangıç süresini kısaltır.

Exception Handling

JVM, çalışma zamanı hatalarını yönetir. Bir hata oluştuğunda programın tamamen çökmesi yerine, hatayı yakalayıp uygun şekilde işleyebilirsin.


JRE: Java Runtime Environment

JRE = JVM + Standart Kütüphaneler.

JRE, bir Java programını çalıştırmak için gereken minimum pakettir. İçinde şunlar var:

  • JVM — Bytecode'u çalıştıran motor

  • Java Sınıf KütüphaneleriString, ArrayList, HashMap, Math gibi temel sınıflar

  • Destek dosyaları — Güvenlik ayarları, zaman dilimi verileri, karakter setleri

JRE'de olmayan şeyler:

  • javac (derleyici) — Kod derleyemezsin

  • javadoc — Dokümantasyon üretemezsin

  • Debugging araçları — Hata ayıklama yapamazsın

Kim JRE Kullanır?

Son kullanıcılar. Yani Java programlarını sadece çalıştırmak isteyen insanlar.

Örneğin, bir banka müşterisinin bilgisayarına Java tabanlı bir uygulama kurulacaksa, o bilgisayara JRE yüklemek yeterlidir. Müşterin kod yazmayacak ki, JDK'ya ihtiyacı yok.

⚠️ Dikkat: Java 11'den itibaren Oracle, ayrı bir JRE dağıtımı sunmayı bıraktı. Artık JDK indiriyorsun, her şey onun içinde. Ama kavramsal olarak JRE hâlâ geçerli — JDK'nın bir alt kümesi olarak düşünebilirsin.


JDK: Java Development Kit

JDK = JRE + Geliştirme Araçları.

JDK, Java geliştirici olarak senin indireceğin paket. İçinde her şey var:

JDK'nın İçindekiler

AraçNe Yapar
javacJava kaynak kodunu bytecode'a derler
javaBytecode'u çalıştırır (JVM'i başlatır)
javadocKaynak koddan API dokümantasyonu üretir
jar.class dosyalarını tek bir .jar arşivine paketler
jdbJava debugger — adım adım hata ayıklama
jconsoleJVM'i izleme aracı (bellek, CPU, thread'ler)
jshellJava 9+ ile gelen interaktif shell (REPL)
javapBytecode'u okunabilir hale getiren decompiler

JDK Kurulumunu Doğrulama

JDK'yı kurduktan sonra terminal/komut satırından şu komutları çalıştırarak kontrol edebilirsin:

# Java versiyonunu kontrol et
java -version

# Çıktı örneği:
# openjdk version "21.0.2" 2024-01-16
# OpenJDK Runtime Environment (build 21.0.2+13)
# OpenJDK 64-Bit Server VM (build 21.0.2+13, mixed mode)

# Derleyici versiyonunu kontrol et
javac -version

# Çıktı örneği:
# javac 21.0.2

Eğer java -version çalışıyor ama javac -version çalışmıyorsa, muhtemelen JRE kurmuşsundur, JDK değil. Veya PATH ayarın eksik. Bu konuyu bir sonraki derste detaylıca ele alacağız.


JDK Dağıtımları

"JDK indireceğim" dediğinde karşına birçok seçenek çıkar. Hepsi aynı OpenJDK kaynak kodundan derlenir ama farklı şirketler tarafından paketlenir:

DağıtımSağlayıcıÜcretNot
Oracle JDKOracleTicari kullanımda ücretli olabilirResmi, ama lisans karışık
Eclipse TemurinAdoptiumÜcretsizEn popüler açık kaynak seçenek
Amazon CorrettoAmazonÜcretsizAWS ile iyi entegre
Azul ZuluAzul SystemsÜcretsiz (Community)Geniş platform desteği
GraalVMOracleCommunity sürümü ücretsizÇoklu dil desteği, native derleme
Microsoft OpenJDKMicrosoftÜcretsizAzure ile iyi entegre

💡 İpucu: Bu kursta Eclipse Temurin veya Amazon Corretto kullanmanı öneririm. İkisi de ücretsiz, güvenilir ve topluluk tarafından yaygın olarak kullanılıyor.


Bytecode Derinlemesine

Bytecode konusunu biraz daha açalım çünkü Java'nın platform bağımsızlığını anlamanın anahtarı bu.

.java → .class Süreci

Diyelim ki şu dosyayı yazdın:

// Hesaplama.java
public class Hesaplama {
    public static void main(String[] args) {
        int x = 10;
        int y = 20;
        int toplam = x + y;
        System.out.println("Toplam: " + toplam);
    }
}

Bu dosyayı javac Hesaplama.java komutuyla derlediğinde, aynı dizinde Hesaplama.class dosyası oluşur. Bu .class dosyası bytecode içerir.

.class Dosyasının Anatomisi

Her .class dosyası şu yapıda başlar:

  • Magic Number: 0xCAFEBABE — Evet, gerçekten. Her .class dosyasının ilk 4 byte'ı bu. Kahve teması devam ediyor.

  • Version: Hangi Java versiyonuyla derlendiği

  • Constant Pool: Sabitler tablosu (string'ler, sayılar, sınıf adları)

  • Methods: Metotların bytecode talimatları

  • Fields: Sınıfın alanları

  • Attributes: Ek bilgiler (debug bilgisi, annotation'lar)

# .class dosyasının ilk byte'larını görmek için:
xxd Hesaplama.class | head -3

# Çıktı:
# 00000000: cafe babe 0000 0041 ...
#           ^^^^^^^^
#           Magic Number!

javap ile Bytecode İnceleme

javap komutu, .class dosyasını okunabilir hale getirir:

javap -c Hesaplama.class

Bu komut sana bytecode talimatlarını gösterir. Şimdilik bunu merak gidermek için bil, ileride performans analizi yaparken işine yarayacak.


JVM Mimarisi (Basitleştirilmiş)

JVM'in iç yapısını tamamen anlamak ileri seviye bir konu ama temel bileşenlerini bilmek faydalı:

1. Class Loader (Sınıf Yükleyici)

.class dosyalarını diskten okuyup JVM'in belleğine yükler. Üç aşamalı çalışır:

  • Loading: Bytecode'u okur

  • Linking: Doğrulama ve hazırlama yapar

  • Initialization: Statik değişkenleri başlatır

2. Runtime Data Areas (Çalışma Zamanı Bellek Alanları)

JVM belleği birkaç bölgeye ayırır:

┌────────────────────────────────────────┐
│            JVM Bellek Yapısı            │
├────────────────────────────────────────┤
│  Method Area    │  Sınıf bilgileri,    │
│                 │  statik değişkenler   │
├────────────────────────────────────────┤
│  Heap           │  Nesneler burada     │
│                 │  yaşar (GC buraya    │
│                 │  bakar)              │
├────────────────────────────────────────┤
│  Stack          │  Her thread'in kendi │
│  (Thread başına)│  stack'i var. Lokal  │
│                 │  değişkenler burada  │
├────────────────────────────────────────┤
│  PC Register    │  Şu anda çalışan     │
│                 │  bytecode satırı     │
├────────────────────────────────────────┤
│  Native Method  │  C/C++ ile yazılmış  │
│  Stack          │  native metodlar     │
└────────────────────────────────────────┘

Heap ve Stack en önemli iki alan:

  • Heap: Tüm nesneler burada yaşar. new ile bir şey oluşturduğunda heap'e gider. Garbage Collector heap'i temizler.

  • Stack: Her metod çağrıldığında stack'e bir frame eklenir. Metoddan çıkılınca frame kaldırılır. Lokal değişkenler (int, boolean vs.) burada tutulur.

3. Execution Engine (Çalıştırma Motoru)

Bytecode'u işleyen bileşen. İçinde:

  • Interpreter: Bytecode'u satır satır yorumlar

  • JIT Compiler: Sık çalışan kodu makine koduna derler

  • Garbage Collector: Kullanılmayan nesneleri temizler


Pratik: JVM'in Çalışmasını Gözlemleme

JVM'in ne yaptığını görmek istersen, Java programını çeşitli flag'lerle çalıştırabilirsin:

# Garbage Collector aktivitesini görmek için:
java -verbose:gc Hesaplama

# Yüklenen sınıfları görmek için:
java -verbose:class Hesaplama

# JVM'in hangi JIT derlemelerini yaptığını görmek için:
java -XX:+PrintCompilation Hesaplama

Bu komutları şimdi çalıştırmasan bile bilmende fayda var. İleride performans sorunlarını teşhis ederken bu araçlar hayat kurtarıcı olacak.


JShell: Hızlı Denemeler İçin

Java 9 ile birlikte JShell geldi. Bu bir REPL (Read-Eval-Print Loop) — yani kodu dosyaya yazmadan, satır satır deneyebilirsin.

$ jshell
|  Welcome to JShell -- Version 21.0.2
|  For an introduction type: /help intro

jshell> int x = 5
x ==> 5

jshell> int y = 10
y ==> 10

jshell> x + y
$3 ==> 15

jshell> System.out.println("Merhaba!")
Merhaba!

jshell> /exit

JShell, yeni bir kavramı hızlıca denemek için mükemmel. main metodu yazmana, dosya oluşturmana gerek yok. Direkt kodu yazıp sonucu görüyorsun.


Sık Karıştırılan Noktalar

"Java derlenmiş mi, yorumlanmış mı?"

İkisi de. Java hybrid bir dil:

  1. Önce javac ile derlenir → bytecode oluşur

  2. Sonra JVM tarafından yorumlanır veya JIT ile tekrar derlenir → makine kodu oluşur

"JDK kurunca JRE de gelir mi?"

Evet. JDK, JRE'yi içerir. JRE de JVM'i içerir. JDK kurduğunda hepsine sahipsin.

"Her bilgisayarda ayrı JVM mı var?"

Evet. Windows için Windows JVM, Mac için Mac JVM, Linux için Linux JVM var. Ama senin bytecode'un hepsinde aynı şekilde çalışır. Bu detayı JVM geliştiricileri hallediyor.

"Bytecode ile makine kodu aynı şey mi?"

Hayır. Bytecode, JVM'in anladığı ara format. Makine kodu, CPU'nun doğrudan çalıştırdığı talimatlar. JVM, bytecode'u makine koduna çevirir.


Diğer JVM Dilleri

JVM sadece Java çalıştırmaz. JVM'in anladığı bytecode'u üreten başka diller de var:

DilÖzelliği
KotlinModern, özlü. Android'in resmi dili. Java ile %100 uyumlu
ScalaFonksiyonel + OOP. Büyük veri (Spark) dünyasında yaygın
GroovyDinamik tipli. Gradle build aracının dili
ClojureFonksiyonel, Lisp ailesi. Eşzamanlılık odaklı

Bu dillerin hepsi aynı JVM üzerinde çalışır ve Java kütüphanelerini kullanabilir. Java öğrendiğinde bu dillere geçiş çok kolay olur.


Garbage Collection Nasıl Çalışır? (Basitleştirilmiş)

Garbage Collector (GC), JVM'in en kritik bileşenlerinden biri. Temel mantık şu:

  1. Bir nesne oluşturulduğunda heap'te yer ayrılır.

  2. GC periyodik olarak heap'i tarar.

  3. Artık erişilemeyen nesneleri tespit eder (hiçbir değişken onları referans etmiyor).

  4. Bu nesnelerin belleğini serbest bırakır.

public class GCOrnegi {
    public static void main(String[] args) {
        String a = new String("Birinci");    // Heap'te "Birinci" oluştu
        String b = new String("İkinci");     // Heap'te "İkinci" oluştu

        a = b;  // a artık "İkinci"yi referans ediyor
                // "Birinci" artık erişilemez → GC temizleyecek

        // Bu noktada heap'te "Birinci" hâlâ var
        // ama kimse ona erişemiyor
        // GC bir sonraki çalışmasında onu temizleyecek
    }
}

GC'nin ne zaman çalışacağını tam olarak bilemezsin. JVM kendi kararını verir. System.gc() ile "lütfen çalış" diyebilirsin ama JVM bunu garanti etmez.


Performans: JVM Optimizasyonları

JVM, kodunu senin farkında bile olmadan optimize eder:

Inline Expansion: Küçük metodları çağrıldıkları yere kopyalar, metod çağrı maliyetini ortadan kaldırır.

Dead Code Elimination: Hiçbir zaman çalışmayacak kodu tespit edip atlar.

Loop Unrolling: Döngüleri açarak tekrarlanan kontrol maliyetini azaltır.

Escape Analysis: Bir nesnenin metod dışına çıkmadığını tespit ederse, heap yerine stack'te oluşturur (çok daha hızlı).

Bu optimizasyonlar sayesinde Java, C/C++ ile karşılaştırılabilir performans seviyelerine ulaşabilir. Özellikle uzun süre çalışan sunucu uygulamalarında JIT derlemesi tam potansiyeline ulaşır.


Özet

  • JVM (Java Virtual Machine): Bytecode'u çalıştıran motor. Platform bağımsızlığın sırrı — her OS için ayrı JVM var ama bytecode hepsinde aynı çalışır.

  • JRE (Java Runtime Environment): JVM + standart kütüphaneler. Java programlarını çalıştırmak için yeterli ama geliştirme yapamazsın.

  • JDK (Java Development Kit): JRE + geliştirme araçları (javac, javadoc, jdb...). Geliştirici olarak her zaman JDK kurarsın.

  • Bytecode: Java kodunun derlenmiş hali. Platform bağımsız ara format. .class dosyalarında saklanır.

  • JIT Compiler: Sık çalışan kodu doğrudan makine koduna derler. Java'nın performansının sırrı.

  • Garbage Collector: Kullanılmayan nesneleri otomatik temizler. Manuel bellek yönetimi derdi yok.