← Kursa Dön
📄 Text · 18 min

Garbage Collection Detayları

C veya C++ yazdıysan, bellekle bizzat uğraştığını bilirsin: malloc ile alan ayırırsın, free ile geri verirsin. Bir gün free çağırmayı unutursun — memory leak. Bir gün aynı alanı iki kere free edersin — double free, program çöker. Bir gün free edilmiş alana erişirsin — dangling pointer, güvenlik açığı.

Java bunu senin için otomatik yapar. Garbage Collector (GC), artık kullanılmayan nesneleri tespit eder ve belleklerini geri kazanır. Sen sadece new ile nesne oluşturursun, gerisini JVM halleder.

GC'yi bir apartmanın çöp toplama servisi gibi düşün. Sen çöpünü kapının önüne bırakırsın, servis gelip alır. Ne zaman gelir, nasıl toplar, nereden başlar — bunlar servisin iç detayı. Ama eğer çöp servisi yavaşlarsa ya da kapasitesini aşarsa, apartmanın (uygulamanın) yaşam kalitesi düşer. Bu yüzden GC'nin nasıl çalıştığını bilmek, performans sorunlarını teşhis etmek için kritiktir.


Neden Garbage Collection?

Manuel bellek yönetiminin sorunlarını zaten saydık. Ama GC'nin otomatik olması "bedava" anlamına gelmez. GC çalışırken CPU zamanı harcar. Bazı GC algoritmaları uygulamayı tamamen durdurur (Stop-the-World pause). Bu duraklamalar milisaniyelerden saniyelere kadar çıkabilir.

Dolayısıyla GC, güvenlik ve kolaylık ile performans arasında bir trade-off'tur. Java bu trade-off'u senin lehine optimize etmek için yıllar içinde birçok GC algoritması geliştirdi. Hangisini seçeceğin, uygulamanın karakterine bağlı.


Heap Yapısı

Java'da nesneler heap adı verilen bellek alanında yaşar. Heap sabit bir bölge değil — JVM başlangıçta belirli bir boyutta ayırır, ihtiyaç oldukça büyütür (bir üst sınıra kadar). GC'yi anlamak için heap'in iç yapısını bilmek gerekir.

Generational Hypothesis

GC'nin temelinde bir gözlem yatar: çoğu nesne kısa ömürlüdür. Bir method içinde oluşturulan geçici nesneler, döngüdeki ara değerler, String birleştirmelerin ara ürünleri — bunların hepsi birkaç milisaniye içinde işlerini bitirir ve çöp olur.

Az sayıda nesne ise uzun yaşar: cache'teki veriler, singleton'lar, veritabanı bağlantı havuzları.

Bu gözlem şu tasarıma yol açar: Heap'i genç ve yaşlı bölgelere ayır. Genç nesneleri sık topla (çoğu zaten ölüdür, hızlı biter). Yaşlı nesneleri nadir topla (çoğu hâlâ yaşıyor, pahalı bir iş).

Heap Bölgeleri

┌─────────────────────────────────────────────────┐
│                    HEAP                          │
│  ┌──────────────────────┐  ┌──────────────────┐ │
│  │     Young Gen         │  │     Old Gen      │ │
│  │  ┌──────┐ ┌────────┐ │  │                  │ │
│  │  │ Eden │ │Survivor│ │  │  Uzun ömürlü     │ │
│  │  │      │ │ S0│ S1 │ │  │  nesneler        │ │
│  │  └──────┘ └────────┘ │  │                  │ │
│  └──────────────────────┘  └──────────────────┘ │
│                                                  │
│  ┌──────────────────────────────────────────┐   │
│  │            Metaspace (off-heap)           │   │
│  │  Sınıf metadata, method bilgileri         │   │
│  └──────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘

Eden: Yeni nesneler burada doğar. new Object() dediğinde nesne Eden'a yerleşir. Eden dolduğunda Minor GC tetiklenir.

Survivor (S0, S1): Minor GC'den sağ kurtulan nesneler buraya taşınır. İki Survivor alanı vardır — her Minor GC'de canlı nesneler birinden diğerine kopyalanır. Bu "ping-pong" sistemi fragmentasyonu önler.

Old Generation (Tenured): Birçok Minor GC'den sağ kurtulan nesneler — yani kanıtlanmış uzun ömürlüler — Old Gen'e terfi (promote) eder. Old Gen dolduğunda Major GC (ya da Full GC) tetiklenir. Bu daha pahalı bir operasyondur.

Metaspace: Java 8 ile PermGen'in yerini aldı. Sınıf tanımları, method metadata'ları burada tutulur. Heap'in parçası değildir — native memory'de yaşar. Varsayılan olarak sınırsız büyüyebilir (-XX:MaxMetaspaceSize ile sınırlanır).

Minor GC vs Major GC

ÖzellikMinor GCMajor GC (Full GC)
Temizlediği alanYoung GenTüm heap (Young + Old)
SıklıkSık (saniyeler)Nadir (dakikalar)
SüreKısa (ms)Uzun (yüzlerce ms - saniyeler)
Stop-the-WorldKısaUzun

GC Algoritmaları

Java'da birden fazla GC algoritması var. Her biri farklı bir iş yükü için optimize edilmiş.

Serial GC (-XX:+UseSerialGC)

En basit GC. Tek thread kullanır. Stop-the-World süreleri uzundur. Küçük heap'lerde (<100MB) ve tek çekirdekli sistemlerde kullanılır. Bugün genellikle sadece çok küçük uygulamalar veya container'lar için tercih edilir.

java -XX:+UseSerialGC -jar myapp.jar

Parallel GC (-XX:+UseParallelGC)

Serial'in çok thread'li versiyonu. Birden fazla GC thread'i aynı anda çalışır — toplama süresi kısalır. Throughput (birim zamanda yapılan iş miktarı) optimize eder. Java 8'e kadar varsayılan GC buydu.

java -XX:+UseParallelGC -XX:ParallelGCThreads=4 -jar myapp.jar

Stop-the-World duraklamaları hâlâ var ama paralel çalışma sayesinde daha kısa sürer.

G1 GC (-XX:+UseG1GC) — Varsayılan

Java 9'dan itibaren varsayılan GC. Region tabanlı tasarımıyla hem throughput hem de düşük duraklama hedefler. İleriki bölümde detaylı inceleyeceğiz.

ZGC (-XX:+UseZGC)

Java 15'te production-ready oldu. Ultra düşük latency hedefler — duraklamalar genellikle 1ms altında. Terabaytlarca heap'i yönetebilir. Java 21 ile generational versiyonu da geldi.

Shenandoah (-XX:+UseShenandoahGC)

Red Hat'in geliştirdiği düşük latency GC. ZGC'ye benzer hedefleri var — çok kısa duraklamalar. OpenJDK'da mevcut, Oracle JDK'da yok.

GCHedefDuraklamaHeap Boyutu
SerialBasitlikUzunKüçük (<100MB)
ParallelThroughputOrtaOrta-büyük
G1DengeliOrta-kısaOrta-büyük
ZGCDüşük latency<1msBüyük (TB'ler)
ShenandoahDüşük latency<10msOrta-büyük

G1 GC Detaylı

G1 (Garbage-First), modern Java uygulamalarının varsayılan GC'sidir. Adı "çöp-önce" anlamına gelir — en çok çöp barındıran bölgeleri önce toplar.

Region Tabanlı Tasarım

Klasik GC'ler heap'i Young ve Old olarak iki büyük bloğa ayırır. G1 ise heap'i eşit boyutta region'lara böler (varsayılan: 1-32MB arası, heap boyutuna göre otomatik ayarlanır).

┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│  E  │  E  │  S  │  O  │  O  │  E  │  H  │  -  │
├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│  O  │  -  │  E  │  O  │  O  │  -  │  H  │  O  │
├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│  E  │  O  │  -  │  S  │  O  │  E  │  -  │  O  │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

E = Eden  |  S = Survivor  |  O = Old  |  H = Humongous  |  - = Boş

Her region tek bir role atanır ama bu rol değişebilir. Bir region önce Eden olabilir, sonra boşaltılıp Old olabilir. Bu esneklik, G1'e büyük esneklik sağlar.

Humongous region: Bir nesne region boyutunun yarısından büyükse "humongous" (devasa) sayılır ve özel bölgelere yerleştirilir. Çok büyük array'ler veya byte buffer'lar bu kategoriye girer.

G1 Çalışma Döngüsü

G1 üç ana fazda çalışır:

1. Young-only Collection: Eden dolduğunda çalışır. Canlı nesneler Survivor veya Old region'lara taşınır. Eden region'ları boşaltılır.

2. Concurrent Marking: Old Gen'in doluluk oranı bir eşiği geçtiğinde (-XX:InitiatingHeapOccupancyPercent, varsayılan %45) concurrent marking başlar. Uygulama çalışırken arka planda hangi nesnelerin canlı olduğunu belirler.

3. Mixed Collection: Concurrent marking tamamlandıktan sonra, G1 hem Young hem de en çok çöp barındıran Old region'ları temizler. "Mixed" denmesinin sebebi budur — sadece Young değil, seçilmiş Old region'lar da temizlenir.

// G1'in davranışını gösteren basit bir senaryo
import java.util.ArrayList;
import java.util.List;

class Main {
    public static void main(String[] args) {
        List<byte[]> longLived = new ArrayList<>();

        for (int i = 0; i < 1000; i++) {
            // Kısa ömürlü nesneler → Eden'da doğar, Minor GC'de temizlenir
            byte[] temp = new byte[1024 * 100];  // 100KB

            // Her 10 iterasyonda bir uzun ömürlü nesne
            if (i % 10 == 0) {
                longLived.add(new byte[1024 * 500]);  // Old Gen'e promote olur
            }
        }

        System.out.println("Long-lived objects: " + longLived.size());
    }
}

G1 Duraklama Hedefi

G1'in en güçlü özelliği duraklama hedefi koyabilmendir:

java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar myapp.jar

Bu, G1'e "her GC duraklamasını 200ms altında tut" der. G1 buna uymaya çalışır (garanti değildir) — daha az region toplayarak süreyi kısaltır. Ama az toplama, daha sık GC anlamına gelir.

⚠️ Dikkat: MaxGCPauseMillis değerini çok düşük koymak geri tepebilir. G1 her seferinde çok az temizlerse, heap dolmaya devam eder ve sonunda uzun bir Full GC kaçınılmaz olur. Varsayılan 200ms genellikle iyi bir başlangıçtır.


ZGC: Düşük Latency, Büyük Heap

ZGC, Java'nın en modern GC'lerinden biri. Hedefi net: duraklama sürelerini 1ms altında tutmak — heap ne kadar büyük olursa olsun.

Nasıl Çalışır?

ZGC'nin sihri colored pointers ve load barriers tekniklerindedir. Kısaca: nesneleri taşırken uygulamayı durdurmak yerine, nesnelere erişildiği anda (load barrier ile) referansları günceller. Bu sayede GC'nin büyük bölümü uygulama ile eş zamanlı çalışır.

# ZGC kullanımı (Java 17+)
java -XX:+UseZGC -Xmx16g -jar myapp.jar

# Java 21+ ile generational ZGC (önerilen)
java -XX:+UseZGC -XX:+ZGenerational -Xmx16g -jar myapp.jar

Ne Zaman ZGC?

  • Heap boyutu çok büyük (8GB+, hatta TB'ler)

  • Düşük latency kritik (fintech, gaming, real-time sistemler)

  • Uzun GC duraklamaları kabul edilemez

Ne Zaman ZGC Değil?

  • Küçük heap'lerde (&lt;4GB) G1 yeterli ve daha az overhead

  • Throughput en önemli metrikse, Parallel GC daha iyi olabilir

  • ZGC biraz daha fazla CPU kullanır — CPU'nun sınırlı olduğu ortamlarda dikkatli ol


GC Tuning Temelleri

GC tuning, uygulamandan önce ölçüm yapmak demektir. Premature optimization'dan kaçın — önce sorun olduğunu kanıtla, sonra tune et.

Temel JVM Parametreleri

# Minimum ve maksimum heap boyutu
java -Xms512m -Xmx2g -jar myapp.jar

# -Xms: Başlangıç heap boyutu. JVM bu kadar bellek ayırarak başlar.
# -Xmx: Maksimum heap boyutu. Heap bu sınırı aşamaz.

Best practice: -Xms ve -Xmx'i aynı değer yap. Böylece JVM heap'i büyütmek/küçültmek için zaman harcamaz.

# Önerilen başlangıç konfigürasyonu
java -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar myapp.jar

GC Seçimi

SenaryoÖnerilen GC
Genel amaçlı web uygulamasıG1 (varsayılan)
Batch processing, yüksek throughputParallel
Düşük latency, büyük heapZGC
Microservice, küçük containerSerial veya G1

GC Logları Okuma

GC'nin ne yaptığını anlamak için loglarını açmalısın.

Log Açma (Java 9+)

# Temel GC logları
java -Xlog:gc -jar myapp.jar

# Detaylı loglar
java -Xlog:gc*:file=gc.log:time,uptime,level,tags -jar myapp.jar

# Açıklama:
# gc*          → GC ile ilgili tüm tag'ler
# file=gc.log  → Dosyaya yaz
# time,uptime  → Zaman bilgisi ekle

Log Çıktısını Okuma

[0.234s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
                   12M->4M(256M) 3.456ms

[15.678s][info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause)
                    128M->45M(256M) 8.234ms

[45.123s][info][gc] GC(25) Pause Full (G1 Compaction Pause)
                    230M->120M(256M) 456.789ms

Bu satırları okuyalım:

  • GC(0): GC event numarası

  • Pause Young (Normal): Minor GC — Young Gen temizliği

  • 12M->4M(256M): Heap kullanımı 12MB'den 4MB'ye düştü, toplam heap 256MB

  • 3.456ms: Duraklama süresi

Son satırdaki Pause Full dikkat çekici: 456ms'lik bir duraklama. Bu çok uzun — muhtemelen heap yetersiz kalıyor.

Alarm sinyalleri:

  • Full GC sıklığı artıyor → Heap yetersiz veya memory leak var

  • Full GC sonrası heap kullanımı düşmüyor → Memory leak güçlü şüpheli

  • Duraklama süreleri kabul edilemez uzunlukta → GC algoritmasını değiştir veya heap'i büyüt

GC Log Analiz Araçları

Ham logları elle okumak zor. Şu araçlar görselleştirme sağlar:

  • GCViewer: Açık kaynak, GC loglarını grafik olarak gösterir

  • GCEasy (gceasy.io): Online GC log analizi — dosyayı yükle, rapor al

  • JClarity Censum: Detaylı GC analizi


Memory Leak Tespiti

Java'da garbage collector var — ama memory leak yine olabilir. Nasıl mı? Bir nesneye hâlâ referans varsa GC onu toplayamaz. Kullanmayacağın nesnelere referans tutmaya devam edersen, heap yavaş yavaş dolar.

Yaygın Memory Leak Senaryoları

// 1. Static koleksiyona sürekli ekleme, hiç silmeme
public class EventLog {
    private static final List<Event> events = new ArrayList<>();

    public static void log(Event event) {
        events.add(event);  // Sonsuza kadar büyür!
    }
}

// 2. Listener/callback kaydedip çıkarmayı unutmak
button.addActionListener(e -> handleClick());
// Butonu kullanmayı bıraktığında listener hâlâ referans tutar

// 3. Map'e koyup çıkarmayı unutmak (cache senaryosu)
Map<String, HeavyObject> cache = new HashMap<>();
cache.put(key, expensiveComputation());
// Expired entry'leri asla silmiyorsan → leak

Tespit Araçları

jps — Çalışan Java proseslerinin PID'lerini listeler:

jps -l
# 12345 com.example.MyApp
# 12346 jdk.jcmd/sun.tools.jps.Jps

jstat — GC istatistiklerini canlı izler:

# Her 1 saniyede GC istatistikleri
jstat -gcutil 12345 1000

# Çıktı:
#   S0     S1     E      O      M     CCS    YGC  YGCT   FGC  FGCT   CGC  CGCT   GCT
#   0.00  45.23  67.89  78.45  96.12  92.34   42  0.543    3  1.234    8  0.123  1.900
  • E: Eden doluluk oranı (%)

  • O: Old Gen doluluk oranı (%)

  • YGC/YGCT: Young GC sayısı / toplam süre

  • FGC/FGCT: Full GC sayısı / toplam süre

Eğer O sürekli artıyorsa ve Full GC sonrası düşmüyorsa → memory leak.

jmap — Heap dump alır:

# Heap dump oluştur
jmap -dump:format=b,file=heapdump.hprof 12345

# Canlı nesnelerin histogramı
jmap -histo 12345 | head -20

VisualVM — Görsel izleme aracı:

# JDK ile gelmeyebilir, ayrı indirilebilir
visualvm

VisualVM ile heap kullanımını grafiksel olarak izler, heap dump alır, hangi sınıfların en çok bellek kullandığını görürsün. "Monitor" sekmesinde heap'in sürekli arttığını görüyorsan, "Sampler" sekmesine geç ve hangi nesnelerin biriktiğini bul.

💡 İpucu: Production'da memory leak şüphesi varsa, heap dump almadan önce dikkat et — dump dosyası heap kadar büyük olabilir (2GB heap = 2GB dosya). Disk alanı kontrolü yap. Ve dump alma sırasında uygulama kısa süre durabilir.

Heap Dump Analizi

Heap dump'ı Eclipse MAT (Memory Analyzer Tool) ile açmak en yaygın yöntemdir:

  1. MAT'ı aç → heap dump dosyasını yükle

  2. Leak Suspects Report: Otomatik olarak şüpheli nesneleri tespit eder

  3. Dominator Tree: Hangi nesne en çok bellek tutuyor

  4. Histogram: Sınıf bazında nesne sayısı ve bellek kullanımı

// MAT Histogram örnek çıktı:
Class Name                    | Objects | Shallow Heap | Retained Heap
------------------------------------------------------------------
byte[]                        | 125,432 | 45,678,912   | 45,678,912
java.lang.String              |  98,234 |  2,357,616   | 48,036,528
com.example.CacheEntry        |  50,000 | 1,200,000    | 25,000,000  ← Şüpheli!
java.util.HashMap$Node        |  50,123 |  1,603,936   | 26,603,936

50.000 CacheEntry nesnesi ve 25MB retained heap — cache'in temizlenmediği açık.


Finalization ve Cleaner

Java'da finalize() method'u, bir nesne GC tarafından toplanmadan önce çağrılır. Fikir güzel görünüyor: kaynakları temizlemek için son bir şans. Ama pratikte felaket.

Neden finalize() Deprecated?

// YAPMA — finalize() Java 9'da deprecated, Java 18'de removal için işaretlendi
class ResourceHolder {
    private FileInputStream stream;

    @Override
    protected void finalize() throws Throwable {
        stream.close();  // "GC toplamadan önce dosyayı kapat"
        super.finalize();
    }
}

Sorunlar:

1. Çağrılma garantisi yok. JVM kapanırken finalizer'ları çalıştırmayabilir. Kaynak asla temizlenmeyebilir.

2. Ne zaman çağrılacağı belirsiz. GC ne zaman çalışacağını bilmezsin. Dosya handle'ı veya veritabanı bağlantısı dakikalarca, hatta saatlerce açık kalabilir.

3. GC'yi yavaşlatır. Finalizer queue'su ayrı bir thread tarafından işlenir. Nesneler en az iki GC döngüsü yaşar — ilkinde finalizer kuyruğuna girer, ikincisinde toplanır.

4. Exception yutulur. Finalizer içinde exception fırlarsa sessizce yutulur. Haberin bile olmaz.

5. Güvenlik riski. Finalizer attack'lar mümkün — kötü niyetli bir alt sınıf finalizer'da nesneyi yeniden erişilebilir yapabilir.

Doğru Yol: try-with-resources

// DOĞRU — AutoCloseable + try-with-resources
class ResourceHolder implements AutoCloseable {
    private final FileInputStream stream;

    ResourceHolder(String path) throws IOException {
        this.stream = new FileInputStream(path);
    }

    @Override
    public void close() throws IOException {
        stream.close();
    }
}

// Kullanım
try (ResourceHolder holder = new ResourceHolder("data.txt")) {
    // holder'ı kullan
}  // Otomatik olarak close() çağrılır — garanti!

Cleaner API (Java 9+)

Eğer gerçekten bir "son şans temizliği" lazımsa, java.lang.ref.Cleaner kullan:

import java.lang.ref.Cleaner;

class NativeResource implements AutoCloseable {
    private static final Cleaner CLEANER = Cleaner.create();

    private final long nativePointer;
    private final Cleaner.Cleanable cleanable;

    NativeResource() {
        this.nativePointer = allocateNative();  // Native bellek ayır
        // Cleaner'a temizleme aksiyonunu kaydet
        // DİKKAT: Lambda 'this'e referans TUTMAMALI!
        long ptr = this.nativePointer;
        this.cleanable = CLEANER.register(this, () -> freeNative(ptr));
    }

    @Override
    public void close() {
        cleanable.clean();  // Manuel temizlik — tercih edilen yol
    }

    private static native long allocateNative();
    private static native void freeNative(long pointer);
}

Cleaner, finalizer'ın sorunlarının çoğunu çözer: ayrı bir thread'de güvenle çalışır, exception'lar yutulmaz. Ama yine de try-with-resources birincil yolun olmalı — Cleaner sadece bir güvenlik ağıdır.


Pratik: GC Davranışını Gözlemleme

Aşağıdaki programı farklı GC'lerle çalıştırarak farkı gözlemleyebilirsin:

import java.util.ArrayList;
import java.util.List;

class Main {
    public static void main(String[] args) {
        List<byte[]> retained = new ArrayList<>();
        int leakCount = 0;

        for (int i = 0; i < 500; i++) {
            // Kısa ömürlü nesneler — GC tarafından toplanmalı
            for (int j = 0; j < 100; j++) {
                byte[] temp = new byte[1024];  // 1KB
            }

            // Her 5 iterasyonda bir "leak" — retained listesine ekle
            if (i % 5 == 0) {
                retained.add(new byte[1024 * 100]);  // 100KB
                leakCount++;
            }
        }

        System.out.println("Retained objects: " + leakCount);
        System.out.println("Approximate retained size: " +
            (leakCount * 100) + " KB");
    }
}
# G1 ile çalıştır ve GC loglarını gör
javac Main.java
java -Xms64m -Xmx64m -XX:+UseG1GC -Xlog:gc Main

# ZGC ile karşılaştır
java -Xms64m -Xmx64m -XX:+UseZGC -Xlog:gc Main

# Serial ile karşılaştır
java -Xms64m -Xmx64m -XX:+UseSerialGC -Xlog:gc Main

Logları karşılaştırdığında GC event sayısını, duraklama sürelerini ve heap kullanım değişimlerini göreceksin. G1 daha sık ama kısa duraklamalar yapar. Serial daha nadir ama daha uzun. ZGC en kısa duraklamaları verir.


GC Tuning Checklist

Tuning yapmadan önce bu listeyi takip et:

1. Ölç. GC loglarını aç, mevcut durumu anla. Körlemesine parametre değiştirme.

2. Heap boyutunu doğru ayarla. jstat ile Old Gen doluluk oranını izle. Sürekli %80+ ise heap'i büyüt. Full GC çok sık ise de heap muhtemelen küçük.

3. Varsayılanlarla başla. G1 zaten çoğu senaryo için iyi ayarlanmış. İlk denemen her zaman varsayılan parametrelerle olsun.

4. Bir parametreyi değiştir, tekrar ölç. Birden fazla parametreyi aynı anda değiştirme — hangisinin etkili olduğunu bilemezsin.

5. Throughput mu latency mi? Neyi optimize ettiğini bil. İkisini aynı anda en üst düzeye çıkaramazsın — trade-off kaçınılmaz.


Özet

  • Garbage Collection, Java'nın otomatik bellek yönetim sistemidir — new ile oluşturursun, GC gerisini halleder.

  • Heap Young Gen (Eden + Survivor) ve Old Gen olarak bölünür. Çoğu nesne Young Gen'de doğar ve ölür (generational hypothesis).

  • G1 GC varsayılan seçimdir — region tabanlı, duraklama hedefi koyulabilir. ZGC ultra düşük latency için, Parallel yüksek throughput için uygundur.

  • GC loglarını -Xlog:gc ile aç, Full GC sıklığı ve duraklama sürelerini izle. Sürekli artan Old Gen kullanımı memory leak işaretidir.

  • Memory leak tespiti için jstat ile canlı izle, jmap ile heap dump al, Eclipse MAT ile analiz et.

  • finalize() kullanma — try-with-resources ve Cleaner API kullan. Kaynak temizliğini GC'ye bırakma, kendim yönet.