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ğırBytecode'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:
JVM programı çalıştırmaya başlar, ilk başta bytecode'u yorumlar (interpreter).
Hangi kod parçalarının sık çalıştığını takip eder (hot spots = sıcak noktalar).
Sık çalışan kod parçalarını doğrudan makine koduna derler ve önbelleğe alır.
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üphaneleri —
String,ArrayList,HashMap,Mathgibi temel sınıflarDestek dosyaları — Güvenlik ayarları, zaman dilimi verileri, karakter setleri
JRE'de olmayan şeyler:
javac(derleyici) — Kod derleyemezsinjavadoc— Dokümantasyon üretemezsinDebugging 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 |
|---|---|
javac | Java kaynak kodunu bytecode'a derler |
java | Bytecode'u çalıştırır (JVM'i başlatır) |
javadoc | Kaynak koddan API dokümantasyonu üretir |
jar | .class dosyalarını tek bir .jar arşivine paketler |
jdb | Java debugger — adım adım hata ayıklama |
jconsole | JVM'i izleme aracı (bellek, CPU, thread'ler) |
jshell | Java 9+ ile gelen interaktif shell (REPL) |
javap | Bytecode'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.2Eğ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ım | Sağlayıcı | Ücret | Not |
|---|---|---|---|
| Oracle JDK | Oracle | Ticari kullanımda ücretli olabilir | Resmi, ama lisans karışık |
| Eclipse Temurin | Adoptium | Ücretsiz | En popüler açık kaynak seçenek |
| Amazon Corretto | Amazon | Ücretsiz | AWS ile iyi entegre |
| Azul Zulu | Azul Systems | Ücretsiz (Community) | Geniş platform desteği |
| GraalVM | Oracle | Community sürümü ücretsiz | Çoklu dil desteği, native derleme |
| Microsoft OpenJDK | Microsoft | Ücretsiz | Azure 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.classdosyası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.classBu 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.
newile 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 HesaplamaBu 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> /exitJShell, 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:
Önce
javacile derlenir → bytecode oluşurSonra 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 |
|---|---|
| Kotlin | Modern, özlü. Android'in resmi dili. Java ile %100 uyumlu |
| Scala | Fonksiyonel + OOP. Büyük veri (Spark) dünyasında yaygın |
| Groovy | Dinamik tipli. Gradle build aracının dili |
| Clojure | Fonksiyonel, 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:
Bir nesne oluşturulduğunda heap'te yer ayrılır.
GC periyodik olarak heap'i tarar.
Artık erişilemeyen nesneleri tespit eder (hiçbir değişken onları referans etmiyor).
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.
.classdosyaları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.
AI Asistan
Sorularını yanıtlamaya hazır