Eğitim Portalı/Java/JVM: Garbage Collection ve JIT
Java01-java/82-jvm-gc-ve-jit

JVM: Garbage Collection ve JIT

Java'nın "yaz bir kez, her yerde çalış" gücü ve performansının arkasında JVM (Java Virtual Machine) vardır. JVM'in iki kritik mekanizması, programının belleğini ve hızını otomatik olarak yönetir: Garbage Collection (ç…

JVM: Garbage Collection ve JIT

Java'nın "yaz bir kez, her yerde çalış" gücü ve performansının arkasında JVM (Java Virtual Machine) vardır. JVM'in iki kritik mekanizması, programının belleğini ve hızını otomatik olarak yönetir: Garbage Collection (çöp toplama — belleği) ve JIT derleyici (hızı). İkisini de sen elle yönetmezsin; ama nasıl çalıştıklarını anlamak, performanslı ve sızıntısız kod yazmanı sağlar.

Garbage Collection (çöp toplama)

C/C++'ta belleği elle ayırır ve free ile boşaltırsın — unutursan sızıntı, erken boşaltırsan çökme olur. Java'da bunu GC yapar: artık ulaşılamayan (hiçbir referansı kalmayan) nesneleri otomatik tespit edip belleği geri kazanır.

List<byte[]> tutucu = new ArrayList<>();
tutucu.add(new byte[100_000]);   // bellek kullanılır
tutucu.clear();                   // referans gitti -> nesneler çöp
System.gc();                      // GC ÖNERİSİ (garanti değil)

Örnek 1 (./Ornek1.java) Runtime ile bellek kullanımını izler, çöp üretip GC'nin topladığını gösterir ve WeakReference ile zayıf referansları gösterir (yalnızca weak referans kalan nesne GC tarafından toplanabilir — önbellekler için kullanılır).

Önemli noktalar:

  • System.gc() yalnızca öneridir — JVM ne zaman/toplayıp toplamayacağına kendi karar verir. Üretimde elle çağırma.
  • Referans türleri: güçlü (normal), WeakReference ("varsa kullan"), SoftReference (bellek baskısında atılır), PhantomReference (temizlik kancası).
  • GC türleri: G1 (Java'nın varsayılanı), ZGC/Shenandoah (çok düşük gecikme; topic 20'de değindik). Her biri gecikme/verim dengesini farklı kurar.
  • Bellek sızıntısı (Java'da da olur!): İstemeden tutulan referanslar — büyüyen statik koleksiyonlar, kapatılmayan kaynaklar, dinleyici (listener) birikmesi. GC ulaşılabilir nesneyi toplayamaz.

JIT (Just-In-Time) derleyici

JVM, Java kodunu önce platformdan bağımsız bytecode'a derler (javac). Çalışma anında bu bytecode yorumlanır (interpret). Ama JVM, sık çalışan ("hot") metotları tespit edip onları çalışma anında yerel makine koduna derler — işte JIT budur. Sonuç: büyük hızlanma.

Bunun gözlemlenebilir sonucu "ısınma" (warmup): bir metot ilk çağrılarda (yorumlanırken) genelde yavaş, çok çağrıldıktan sonra (JIT derledikten sonra) genelde hızlıdır. JIT ayrıca satır içi alma (inlining), ölü kod eleme, döngü açma gibi agresif optimizasyonlar yapar.

Örnek 2 (./Ornek2.java) aynı işi tur tur ölçer ve önemli bir gerçeği gösterir: sayılar tutarlı bir "hep azalan" eğri vermeyebilir! İlk tur genelde en yavaştır ama sonraki turlar GC duraklamaları, OS zamanlaması ve JIT'in farklı anlarda devreye girmesi yüzünden dalgalanır.

Asıl ders — mikro-benchmark tuzağı: Güvenilir performans ölçümü System.nanoTime() ile elle yapılmaz. Warmup, GC gürültüsü ve JIT optimizasyonları (ölü kod eleme, sabit katlama, inlining) sonucu çarpıtır; bu yüzden naif ölçümler yanıltıcıdır. Doğru ölçüm için JMH (Java Microbenchmark Harness) kullanılır — warmup, çoklu iterasyon ve istatistiksel analizi o doğru yapar.

Bellek alanları (kısa)

JVM belleği başlıca: Heap (nesneler — GC burada çalışır; genç/yaşlı kuşak), Stack (her thread'in metot çağrıları ve yerel değişkenleri), Metaspace (sınıf meta-verisi). OutOfMemoryError (heap dolu) ve StackOverflowError (stack dolu — topic 69) bu alanlarla ilgilidir.

Özet

JVM'in iki otomatik mekanizmasını öğrendik: ulaşılamayan nesneleri toplayan Garbage Collection (referans türleri, GC türleri, sızıntı nedenleri; Örnek 1) ve sık çalışan kodu makine koduna derleyip hızlandıran JIT (warmup etkisi, mikro-benchmark tuzağı; Örnek 2). Bunları anlamak, bellek-dostu ve performanslı Java yazmanın temelidir. Bu, ileri başlıklar batch'ini tamamlıyor.

Kod Örnekleri(2)

Ornek1

çalıştırılabilir
Ornek1.java
1// Ornek1: Garbage Collection (çöp toplama) — bellek kullanımı ve WeakReference.
2// Çalıştırma: java Ornek1.java
3import java.lang.ref.WeakReference;
4import java.util.ArrayList;
5import java.util.List;
6
7public class Ornek1 {
8
9    public static void main(String[] args) {
10        Runtime rt = Runtime.getRuntime();
11        System.out.println("Maks bellek : " + mb(rt.maxMemory()) + " MB");
12        System.out.println("Başlangıç kullanılan: " + mb(kullanilan(rt)) + " MB");
13
14        // Çok sayıda nesne üret (çöp olacaklar). GC bunları toplayacak.
15        List<byte[]> tutucu = new ArrayList<>();
16        for (int i = 0; i < 100; i++) tutucu.add(new byte[100_000]); // ~10 MB
17        System.out.println("100 blok sonrası   : " + mb(kullanilan(rt)) + " MB");
18
19        tutucu.clear();        // referanslar gitti -> nesneler ARTIK ULAŞILAMAZ (çöp)
20        System.gc();           // GC ÖNERİSİ (garanti değil)
21        uyu(100);
22        System.out.println("clear + gc sonrası : " + mb(kullanilan(rt)) + " MB (GC topladı)");
23
24        // WeakReference: zayıf referans. Nesneye SADECE weak referans kalırsa GC onu toplayabilir.
25        Object nesne = new Object();
26        WeakReference<Object> zayif = new WeakReference<>(nesne);
27        System.out.println("\nweak.get() (güçlü ref varken): " + (zayif.get() != null ? "var" : "null"));
28        nesne = null;          // güçlü referansı kaldır -> yalnızca weak kaldı
29        System.gc();
30        uyu(100);
31        System.out.println("weak.get() (güçlü ref yokken): " + (zayif.get() != null ? "var" : "null (GC topladı)"));
32
33        System.out.println("""
34
35                --- Garbage Collection (çöp toplama) ---
36                Java'da belleği SEN yönetmezsin (C/C++ gibi free yok); ULAŞILAMAYAN nesneleri GC otomatik toplar.
37                Bir nesneye hiçbir güçlü referans kalmadığında çöptür ve GC onu temizleyebilir.
38                System.gc() yalnızca ÖNERİDİR (ne zaman/topkanacağı garanti değil); üretimde elle çağırma.
39                WeakReference: 'varsa kullan, GC alırsa sorun değil' (önbellekler). SoftReference: bellek baskısında atılır.
40                GC türleri: G1 (varsayılan), ZGC/Shenandoah (düşük gecikme - topic 20). Bellek sızıntısı = istemeden
41                tutulan referanslar (statik koleksiyonlar, kapanmayan kaynaklar).""");
42    }
43
44    static long kullanilan(Runtime rt) { return rt.totalMemory() - rt.freeMemory(); }
45    static long mb(long b) { return b / (1024 * 1024); }
46    static void uyu(long ms) { try { Thread.sleep(ms); } catch (InterruptedException ignored) {} }
47}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek2

çalıştırılabilir
Ornek2.java
1// Ornek2: JIT derleyici — "ısınma" (warmup) ve neden elle mikro-benchmark GÜVENİLMEZ.
2// Çalıştırma: java Ornek2.java
3public class Ornek2 {
4
5    // Sıcak (hot) metot: çok çağrılınca JIT bunu yerel makine koduna derler.
6    static long hesapla(int n) {
7        long toplam = 0;
8        for (int i = 0; i < n; i++) toplam += (i * 31L) ^ (i >> 2);
9        return toplam;
10    }
11
12    public static void main(String[] args) {
13        int N = 2_000_000;
14        long kontrol = 0;
15
16        // Aynı işi arka arkaya birkaç tur ölç. İLK turlar genelde yavaştır (kod yorumlanıyor,
17        // JIT henüz derlememiş); sonraki turlar JIT derlemesi + önbellek ile genelde hızlanır.
18        // ANCAK sayılar tura/makineye göre DEĞİŞİR ve gürültülüdür — asıl ders budur.
19        System.out.println("Tur tur çalıştırma süreleri (µs):");
20        long[] sureler = new long[8];
21        for (int tur = 0; tur < sureler.length; tur++) {
22            long t0 = System.nanoTime();
23            kontrol += hesapla(N);
24            sureler[tur] = (System.nanoTime() - t0) / 1_000;
25            System.out.printf("  tur %d: ~%5d µs%n", tur + 1, sureler[tur]);
26        }
27
28        System.out.println("(kontrol toplamı kullanıldı: " + (kontrol != 0) + ")");
29        System.out.printf("İlk tur ~%d µs, son tur ~%d µs%n", sureler[0], sureler[sureler.length - 1]);
30
31        System.out.println("""
32
33                --- JIT (Just-In-Time) derleyici ---
34                JVM kodu önce BYTECODE olarak YORUMLAR; sık çalışan ("hot") metotları çalışma anında
35                YERLEŞİK MAKİNE KODUNA derler (JIT) -> büyük hızlanma. Bu yüzden 'ısınma' (warmup) vardır:
36                ilk turlar genelde yavaş, sonraki turlar hızlıdır.
37                AMA yukarıdaki sayılara dikkat: tutarlı bir 'hep azalan' eğri GÖREMEYEBİLİRSİN!
38                Çünkü elle ölçüm GÜRÜLTÜLÜDÜR: GC duraklamaları, OS zamanlaması, JIT'in farklı anlarda
39                devreye girmesi, ölü-kod eleme ve inlining sonucu çarpıtır.
40                İŞTE ASIL DERS: güvenilir performans ölçümü elle System.nanoTime ile YAPILMAZ.
41                JMH (Java Microbenchmark Harness) kullan — warmup, çoklu iterasyon ve istatistik onunla doğru yapılır.""");
42    }
43}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.