JVM ve Bellek Yönetimi
Giriş
Java'nın en büyük vaatlerinden biri şuydu: "Bellek yönetimiyle uğraşma, ben hallederim." C/C++ dünyasından gelen insanlar için bu bir devrimdi. Ama "ben hallederim" demek "hiç düşünme" demek değil. JVM'in belleği nasıl yönettiğini anlamak, performans sorunlarını çözmek ve production'da gece 3'te çalan alarmları önlemek için kritik.
Bu derste JVM'in iç dünyasına dalacağız. Stack ve Heap nedir, Garbage Collector nasıl çalışır, hangi GC algoritması ne zaman seçilir — hepsini konuşacağız.
JVM Mimarisi — Büyük Resim
JVM bir sanal makine. Yazdığın Java kodu önce bytecode'a derlenir (.class dosyaları), sonra JVM bu bytecode'u çalıştırır. Ama çalıştırırken bellekte ciddi bir organizasyon var.
JVM belleği kabaca şu bölgelere ayrılır:
Stack — Method çağrıları ve yerel değişkenler
Heap — Nesneler (objects)
Metaspace — Sınıf bilgileri (Java 8 sonrası)
Code Cache — JIT derlenmiş native kod
Şimdilik Stack ve Heap'e odaklanalım çünkü günlük hayatında en çok bunlarla muhatap olacaksın.
Stack vs Heap
Analoji: Mutfak Tezgahı vs Buzdolabı
Stack'i mutfak tezgahı gibi düşün. Yemek yaparken aktif olarak kullandığın malzemeler tezgahta durur. Yemek bitince tezgahı temizlersin. Her şey düzenli, hızlı erişilir, ama alan sınırlı.
Heap ise buzdolabı. Büyük, kapasiteli, her şeyi atabilirsin. Ama bir şey bulmak tezgaha göre daha yavaş. Ve arada bir temizlemen (Garbage Collection) gerekir, yoksa bozulan şeyler yer kaplamaya devam eder.
Stack Detaylı
Stack, her thread için ayrı oluşturulur. Yani 10 thread varsa 10 ayrı stack var. LIFO (Last In, First Out) prensibiyle çalışır.
Stack'te neler tutulur?
Primitive değişkenler (int, double, boolean...)
Object reference'ları (nesnenin kendisi değil, adresi)
Method çağrı bilgileri (stack frame)
public class StackOrnek {
public static void main(String[] args) {
int sayi = 42; // Stack'te: sayi = 42
String isim = "Ali"; // Stack'te: isim -> Heap'teki String nesnesinin adresi
hesapla(sayi); // Yeni stack frame oluşur
}
static int hesapla(int x) {
int sonuc = x * 2; // Stack'te: x = 42, sonuc = 84
return sonuc; // Method bitince bu frame stack'ten silinir
}
}Her method çağrıldığında stack'e bir frame eklenir. Method bitince frame otomatik silinir. Temizlik bedava — GC'ye gerek yok.
Stack'in boyutu sınırlıdır (varsayılan genelde 512KB-1MB). Sonsuz recursive çağrı yaparsan StackOverflowError alırsın:
public static void sonsuzDongu() {
sonsuzDongu(); // Her çağrı yeni frame ekler
// Sonunda: java.lang.StackOverflowError
}Heap Detaylı
Heap, tüm thread'ler tarafından paylaşılır. new keyword'üyle oluşturduğun her nesne burada yaşar.
public class HeapOrnek {
public static void main(String[] args) {
// "Ali" nesnesi Heap'te oluşur
// ogrenci reference'ı Stack'te tutulur
Ogrenci ogrenci = new Ogrenci("Ali", 20);
// Yeni bir nesne daha Heap'te
List<String> liste = new ArrayList<>();
liste.add("Java");
}
}Heap çok daha büyüktür (GB'larca olabilir) ama yönetimi karmaşıktır. İşte burada Garbage Collector devreye girer.
Karşılaştırma Tablosu
| Özellik | Stack | Heap |
|---|---|---|
| Boyut | Küçük (MB) | Büyük (GB) |
| Hız | Çok hızlı | Nispeten yavaş |
| Thread | Her thread'e özel | Tüm thread'ler paylaşır |
| Temizlik | Otomatik (frame pop) | Garbage Collector |
| İçerik | Primitive + reference | Nesneler |
| Hata | StackOverflowError | OutOfMemoryError |
Heap'in İç Yapısı — Generational Model
JVM Heap'i tek düz bir alan değil. Nesne yaşına göre bölgelere ayrılmış.
Young Generation
Yeni oluşturulan nesneler burada başlar. Çoğu nesne kısa ömürlüdür — bir method içinde oluşur, method bitince artık kimse referans etmez. Bu gözleme "Weak Generational Hypothesis" denir: nesnelerin çoğu genç yaşta ölür.
Young Generation üç bölümden oluşur:
Eden Space — Yeni nesneler burada doğar
Survivor 0 (S0) — İlk GC'den sağ çıkanlar
Survivor 1 (S1) — İkinci GC'den sağ çıkanlar
Old Generation (Tenured)
Birkaç GC döngüsünden sağ çıkan nesneler buraya terfi eder. Buradaki nesneler genelde uzun ömürlüdür: cache'ler, connection pool'lar, singleton'lar gibi.
Akış Şeması
new Object() → Eden Space
↓ (Minor GC)
Survivor 0 ↔ Survivor 1
↓ (Birkaç GC sağ kalırsa)
Old Generation
↓ (Major/Full GC ile temizlenir)💡 Minor GC sadece Young Generation'ı temizler ve çok hızlıdır (milisaniye). Major GC (veya Full GC) tüm Heap'i tarar ve çok daha yavaştır.
Garbage Collector — Nasıl Çalışır?
Temel Prensip: Erişilebilirlik (Reachability)
GC, "Bu nesneye kimse erişebiliyor mu?" sorusunu sorar. Erişilemez nesneler çöp sayılır.
Erişilebilirlik GC Roots'tan başlayarak kontrol edilir. GC Roots şunlardır:
Aktif thread'lerdeki yerel değişkenler
Static değişkenler
JNI referansları
public class GCOrnek {
public static void main(String[] args) {
Object a = new Object(); // a -> Nesne1 (erişilebilir)
Object b = new Object(); // b -> Nesne2 (erişilebilir)
a = null; // Nesne1'e artık kimse erişemiyor → GC temizleyebilir
b = a; // b artık null, Nesne2'ye erişim yok → GC temizleyebilir
}
}Mark and Sweep
En temel GC algoritması iki aşamadan oluşur:
Mark — GC Roots'tan başla, erişilebilir tüm nesneleri işaretle
Sweep — İşaretlenmemiş nesneleri sil
Bazı GC'ler üçüncü adım ekler:
Compact — Hayatta kalan nesneleri yan yana diz (fragmentation'ı önle)
Stop-the-World (STW)
GC çalışırken uygulamanın durması gerekebilir. Buna Stop-the-World pause denir. Neden? Çünkü GC nesneleri tararken, uygulama aynı anda yeni nesne oluşturur veya referansları değiştirirse tutarsızlık olur.
Modern GC'lerin amacı bu duraklamaları minimuma indirmek.
GC Algoritmaları
Serial GC
En basit GC. Tek thread kullanır. Stop-the-World süreleri uzundur.
-XX:+UseSerialGCNe zaman? Küçük uygulamalar, düşük bellek ortamları, container'larda sınırlı kaynak varsa.
Parallel GC (Throughput Collector)
Serial GC'nin çok thread'li versiyonu. Birden fazla GC thread'i paralel çalışır. Java 8'in varsayılan GC'siydi.
-XX:+UseParallelGCNe zaman? Batch processing, throughput önemliyse ve kısa duraklamalar tolere edilebiliyorsa.
G1 GC (Garbage-First)
Java 9'dan itibaren varsayılan GC. Heap'i eşit boyutlu region'lara böler. Hangi region'da en çok çöp varsa önce onu toplar (adı buradan gelir: Garbage-First).
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 // Hedef duraklama süresiG1'in güzelliği: Bir hedef duraklama süresi veriyorsun, G1 bu süreye uymaya çalışıyor.
Ne zaman? Çoğu production uygulaması için iyi bir varsayılan. 4GB+ Heap boyutlarında özellikle etkili.
// G1 region yapısı konsepti
// Heap: [E][E][S][O][O][E][H][O][S][E]...
// E = Eden, S = Survivor, O = Old, H = Humongous (büyük nesne)
// Her region bağımsız toplanabilirZGC
Java 15'te production-ready oldu. Hedefi: 1ms altında duraklama süreleri, TB boyutunda Heap desteği.
-XX:+UseZGCZGC bunu concurrent çalışarak başarır — uygulama çalışırken GC de çalışır, neredeyse hiç durma yok.
Ne zaman? Düşük latency kritikse (trading sistemleri, real-time uygulamalar), çok büyük Heap boyutları.
Karşılaştırma
| GC | STW Süresi | Throughput | Heap Boyutu | Java |
|---|---|---|---|---|
| Serial | Uzun | Düşük | Küçük | Tüm versiyonlar |
| Parallel | Orta | Yüksek | Orta | Tüm versiyonlar |
| G1 | Kontrollü | İyi | Orta-Büyük | 9+ varsayılan |
| ZGC | <1ms | İyi | Çok büyük | 15+ |
⚠️ Dikkat: GC seçimi uygulamanın doğasına göre yapılır. "En iyi GC" diye bir şey yok. Throughput mu önemli, latency mi? Heap ne kadar büyük? Bu soruları yanıtla, sonra seç.
Memory Leak — Java'da Olur mu?
"Java'da GC var, memory leak olmaz" diye düşünüyorsan yanılıyorsun. GC sadece erişilemeyen nesneleri temizler. Erişilebilir ama gereksiz nesneler temizlenmez. İşte Java'daki memory leak budur.
Yaygın Memory Leak Nedenleri
1. Koleksiyonlara Ekleme, Silmemeyi Unutma
public class LeakOrnek {
private static List<byte[]> cache = new ArrayList<>();
public static void islemYap() {
// Her çağrıda 1MB ekleniyor ama hiç silinmiyor
cache.add(new byte[1024 * 1024]);
}
// cache static olduğu için GC Roots'tan erişilebilir
// → GC asla temizlemez → OutOfMemoryError
}2. Listener/Callback Temizlememek
public class EventManager {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
// removeListener yok! Eklenen listener'lar sonsuza kadar yaşar
}3. Kapatılmayan Kaynaklar
// KÖTÜ — Connection kapatılmıyor
public void veriOku() {
Connection conn = DriverManager.getConnection(url);
// ... bir şeyler yap
// conn.close() yok! Connection nesnesi ve bağlı her şey Heap'te kalır
}
// İYİ — try-with-resources
public void veriOku() {
try (Connection conn = DriverManager.getConnection(url)) {
// ... bir şeyler yap
} // Otomatik kapanır
}4. Inner Class Referansları
public class Outer {
private byte[] buyukVeri = new byte[10_000_000]; // 10MB
// Non-static inner class, Outer'a implicit referans tutar
class Inner {
void birSeyYap() { }
}
public Inner getInner() {
return new Inner(); // Inner yaşadığı sürece Outer da yaşar (10MB!)
}
}💡 Çözüm: Inner class'ı
staticyapabiliyorsan yap. Static inner class, outer class'a referans tutmaz.
Memory Leak Tespit Araçları
VisualVM — JDK ile birlikte gelir, Heap dump alıp analiz edebilirsin
JConsole — Basit monitoring
Eclipse MAT (Memory Analyzer Tool) — Heap dump analizi için güçlü
jmap — Komut satırından Heap dump
# Çalışan JVM'in Heap dump'ını al
jmap -dump:format=b,file=heapdump.hprof <pid>
# Heap kullanım özeti
jmap -heap <pid>JVM Tuning Temelleri
Heap Boyutu Ayarlama
# Minimum ve maksimum Heap boyutu
java -Xms512m -Xmx2g MyApp
# -Xms: Başlangıç Heap boyutu
# -Xmx: Maksimum Heap boyutuGenel kural: -Xms ve -Xmx aynı değer yapılırsa Heap'in runtime'da büyümesi/küçülmesi engellenir. Production'da bu genelde tercih edilir.
Stack Boyutu
# Thread başına Stack boyutu
java -Xss1m MyAppDerin recursive çağrılar yapıyorsan Stack boyutunu artırabilirsin. Ama dikkat — her thread için ayrı Stack ayrılır, 1000 thread × 1MB = 1GB sadece Stack için.
GC Loglama
# Java 9+ GC log
java -Xlog:gc*:file=gc.log:time,uptime,level,tags MyApp
# Çıktı örneği:
# [0.015s][info][gc] Using G1
# [1.234s][info][gc] GC(0) Pause Young (Normal) 24M->8M(256M) 3.456msGC logları okumak önemli bir beceri. Pause süreleri, Heap kullanımı ve GC sıklığı buradan takip edilir.
Temel Tuning Stratejisi
Ölç — GC loglarını aç, metrikleri topla
Anla — Duraklama süreleri kabul edilebilir mi? OOM oluyor mu?
Ayarla — Heap boyutu, GC algoritması, GC parametreleri
Tekrar ölç — Değişiklik işe yaradı mı?
# Tipik bir production başlatma komutu
java \
-Xms4g -Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/app/ \
-Xlog:gc*:file=/var/log/app/gc.log:time \
-jar myapp.jar⚠️ Dikkat: Premature optimization yapma. Önce uygulamayı yaz, sonra performans sorunu varsa profiling yap. GC tuning son çare olmalı — genelde kod seviyesinde düzeltmeler daha etkili.
Metaspace
Java 8 öncesi sınıf bilgileri PermGen (Permanent Generation) adlı sabit boyutlu bir alanda tutulurdu. Boyutu dolunca OutOfMemoryError: PermGen space alırdın.
Java 8 ile PermGen kaldırıldı, yerine Metaspace geldi. Metaspace native bellekte yaşar ve varsayılan olarak sınırsız büyüyebilir (OS belleği bitene kadar).
# Metaspace sınırı koymak istersen
java -XX:MaxMetaspaceSize=256m MyAppMetaspace genelde sorun olmaz. Ama dinamik olarak çok sayıda sınıf oluşturan uygulamalarda (proxy framework'ler, bytecode manipulation) dikkat etmek gerekir.
Pratik: Bellek Durumunu Koddan Okuma
public class BellekDurumu {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory(); // -Xmx değeri
long totalMemory = runtime.totalMemory(); // Şu an ayrılmış Heap
long freeMemory = runtime.freeMemory(); // Boş alan
long usedMemory = totalMemory - freeMemory; // Kullanılan
System.out.println("Max Heap: " + mbFormat(maxMemory));
System.out.println("Total: " + mbFormat(totalMemory));
System.out.println("Used: " + mbFormat(usedMemory));
System.out.println("Free: " + mbFormat(freeMemory));
}
static String mbFormat(long bytes) {
return (bytes / (1024 * 1024)) + " MB";
}
}String Pool — Özel Bir Heap Bölgesi
String'ler Java'da özel muamele görür. String literal'leri Heap'teki String Pool adlı özel bir alanda tutulur.
String s1 = "Merhaba"; // String Pool'da oluşturulur
String s2 = "Merhaba"; // Pool'daki aynı nesneyi işaret eder
String s3 = new String("Merhaba"); // Heap'te AYRI bir nesne
System.out.println(s1 == s2); // true — aynı nesne
System.out.println(s1 == s3); // false — farklı nesneler
System.out.println(s1.equals(s3)); // true — aynı içerikString Pool bellek tasarrufu sağlar. Aynı literal'i 1000 yerde kullansen bile bellekte tek bir nesne vardır. new String() ile oluşturulanlar Pool'a girmez — ama intern() ile ekleyebilirsin:
String s3 = new String("Merhaba");
String s4 = s3.intern(); // Pool'daki versiyonu döndürür
System.out.println(s1 == s4); // true💡 Not: Java 7'den itibaren String Pool Heap'te yaşar (eskiden PermGen'deydi). Bu sayede GC tarafından temizlenebilir.
Escape Analysis ve Stack Allocation
JVM bazı nesneleri Heap yerine Stack'te oluşturabilir. Buna Escape Analysis denir. JIT compiler bir nesnenin method dışına "kaçmadığını" tespit ederse, o nesneyi Stack'te oluşturur.
public int toplam() {
// Bu Point nesnesi method dışına çıkmıyor (escape etmiyor)
// JVM bunu Stack'te oluşturabilir → GC'ye iş düşmez
Point p = new Point(3, 4);
return p.x + p.y;
}Escape Analysis otomatik çalışır, senin bir şey yapman gerekmez. Ama bilmek faydalı — kısa ömürlü, method-local nesneler performans açısından "bedava"ya yakın.
Daha da ileri giderse, JVM nesneyi hiç oluşturmayabilir — field'ları doğrudan register'lara koyar. Buna Scalar Replacement denir.
// JVM bunu optimize edebilir:
Point p = new Point(3, 4);
int sonuc = p.x + p.y;
// Buna:
int sonuc = 3 + 4; // Nesne hiç oluşturulmadı!Weak, Soft ve Phantom Reference
Java'da normal referanslar dışında üç özel referans tipi var. Bunlar GC'nin davranışını etkilemene olanak tanır.
Strong Reference (Normal)
Object obj = new Object(); // Strong reference
// obj erişilebilir olduğu sürece GC temizlemezWeakReference
GC çalıştığında, sadece weak reference'ı olan nesneler hemen temizlenir.
import java.lang.ref.WeakReference;
Object obj = new Object();
WeakReference<Object> weak = new WeakReference<>(obj);
obj = null; // Strong reference kaldırıldı
// Bir sonraki GC'de nesne temizlenir
System.gc(); // GC'yi öner (garanti değil)
System.out.println(weak.get()); // muhtemelen nullKullanım alanı: Cache'ler. WeakHashMap weak reference kullanan bir Map implementasyonu — key'e başka referans kalmayınca entry otomatik silinir.
WeakHashMap<Object, String> cache = new WeakHashMap<>();
Object key = new Object();
cache.put(key, "değer");
key = null; // Key'e başka referans yok
System.gc();
// Bir süre sonra cache boşalırSoftReference
WeakReference'a benzer ama GC daha "nazik" davranır. Bellek yeterliyse temizlemez, bellek azaldığında temizler. Cache için idealdir.
import java.lang.ref.SoftReference;
SoftReference<byte[]> cache = new SoftReference<>(new byte[10_000_000]);
byte[] data = cache.get();
if (data != null) {
// Cache'ten oku
} else {
// GC temizlemiş, yeniden yükle
data = yukle();
cache = new SoftReference<>(data);
}PhantomReference
En zayıf referans tipi. get() her zaman null döner. Nesne temizlendikten sonra bildirim almak için kullanılır. ReferenceQueue ile birlikte çalışır.
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantom = new PhantomReference<>(obj, queue);
obj = null;
System.gc();
// Queue'dan referans alınca nesnenin temizlendiğini bilirsin
Reference<?> ref = queue.poll();
if (ref != null) {
System.out.println("Nesne temizlendi!");
// Kaynak temizleme yapabilirsin
}Phantom reference'lar finalize() yerine kullanılır. Cleaner API de arka planda bu mekanizmayı kullanır.
Referans Gücü Karşılaştırma
| Referans | GC Davranışı | Kullanım |
|---|---|---|
| Strong | Erişilebilirse asla temizlenmez | Normal kullanım |
| Soft | Bellek azaldığında temizlenir | Memory-sensitive cache |
| Weak | İlk GC'de temizlenir | WeakHashMap, listener'lar |
| Phantom | Temizlendikten sonra bildirim | Kaynak temizleme |
JIT Compiler ve Performans
JVM bytecode'u doğrudan yorumlamaz (interpreter). Sık çalışan kod parçalarını native makine koduna derler. Buna Just-In-Time (JIT) Compilation denir.
Nasıl Çalışır?
Uygulama başlangıçta interpreter ile çalışır
JVM her method'un kaç kez çağrıldığını sayar
Bir eşik aşılınca (varsayılan ~10.000) method hot kabul edilir
JIT compiler bu method'u native koda derler
Sonraki çağrılarda native kod çalışır — çok daha hızlı
// Bu method 10.000 kez çağrıldıktan sonra JIT tarafından derlenir
public int topla(int a, int b) {
return a + b;
}C1 ve C2 Compiler
JVM'de iki JIT compiler var:
C1 (Client) — Hızlı derleme, temel optimizasyonlar. Uygulama hızlı başlasın.
C2 (Server) — Yavaş derleme, agresif optimizasyonlar. Uzun süre çalışan uygulamalarda yüksek performans.
Varsayılan modda Tiered Compilation aktif: Önce C1 ile hızlıca derle, sonra çok sıcak method'ları C2 ile daha agresif optimize et.
# JIT derleme bilgisini görmek için
java -XX:+PrintCompilation MyApp
# Çıktı örneği:
# 123 1 java.lang.String::charAt (33 bytes)
# 456 2 com.app.Main::topla (4 bytes)Container Ortamında JVM
Docker ve Kubernetes ile çalışırken JVM'in container kaynaklarını doğru algılaması önemli.
Sorun
Eski JVM versiyonları container'ın bellek limitini görmezdi. Host makinenin 64GB RAM'ini görür, ona göre Heap ayarlardı — sonra container limiti 512MB olduğu için OOM Killer tarafından öldürülürdü.
Çözüm
Java 10+ container limitlerine otomatik uyum sağlar:
# Container'da önerilen ayarlar
docker run -m 512m myapp:latest \
java \
-XX:MaxRAMPercentage=75.0 \
-XX:InitialRAMPercentage=50.0 \
-jar myapp.jar-XX:MaxRAMPercentage=75.0 demek: container'ın bellek limitinin %75'ini Heap için kullan. Kalan %25 Metaspace, Stack, native bellek ve OS için ayrılır.
# Eski -Xmx yerine yüzde bazlı ayar
# Container 1GB ise → Heap max 768MB
# Container 2GB ise → Heap max 1536MB
# Otomatik ölçeklenir!⚠️ Dikkat: Container'da
-Xmxile sabit değer vermek yerine-XX:MaxRAMPercentagekullanmak daha esnektir. Container limiti değiştiğinde JVM otomatik uyum sağlar.
Pratik: OutOfMemoryError Simülasyonu
public class OOMSimulasyon {
public static void main(String[] args) {
List<byte[]> liste = new ArrayList<>();
int sayac = 0;
try {
while (true) {
liste.add(new byte[1024 * 1024]); // Her iterasyonda 1MB
sayac++;
System.out.println(sayac + " MB ayrıldı");
}
} catch (OutOfMemoryError e) {
System.out.println("OOM! " + sayac + " MB'da patladı.");
System.out.println("Hata: " + e.getMessage());
}
}
}# Küçük Heap ile çalıştır
java -Xmx50m OOMSimulasyon
# Çıktı: ~47-48 MB'da OOM alırsınPratik: GC Davranışını Gözlemleme
public class GCGozlem {
public static void main(String[] args) throws InterruptedException {
System.out.println("Başlangıç bellek: " + usedMB() + " MB");
// Çok sayıda geçici nesne oluştur
for (int i = 0; i < 100_000; i++) {
String s = new String("Geçici nesne #" + i);
}
System.out.println("Nesneler oluşturuldu: " + usedMB() + " MB");
// GC'yi öner
System.gc();
Thread.sleep(1000);
System.out.println("GC sonrası: " + usedMB() + " MB");
}
static long usedMB() {
Runtime rt = Runtime.getRuntime();
return (rt.totalMemory() - rt.freeMemory()) / (1024 * 1024);
}
}# GC logları ile çalıştır
java -Xlog:gc* GCGozlemfinalize() — Neden Kullanılmamalı
Java'da finalize() method'u, nesne GC tarafından temizlenmeden önce çağrılırdı. Ama bu method ciddi sorunlara yol açar:
Ne zaman çağrılacağı garanti değil
GC'yi yavaşlatır
Nesneyi yanlışlıkla tekrar erişilebilir yapabilir (resurrection)
Java 9'da deprecated, Java 18'de kaldırılmak üzere işaretlendi
// YAPMA
@Override
protected void finalize() throws Throwable {
// Kaynak temizleme kodu buraya konmamalı
connection.close();
}
// BUNUN YERİNE: try-with-resources veya Cleaner API kullanÖzet
Stack thread'e özel, küçük, hızlı — primitive ve reference tutar. Heap paylaşımlı, büyük — nesneler burada yaşar
Heap, Young (Eden + Survivor) ve Old generation'a ayrılır. Çoğu nesne genç yaşta ölür
GC, erişilemeyen nesneleri temizler. Mark → Sweep → (Compact) adımlarıyla çalışır
G1 GC çoğu uygulama için iyi varsayılan. ZGC düşük latency istiyorsan tercih et
Java'da memory leak olur — koleksiyonlarda biriken nesneler, kapatılmayan kaynaklar, static referanslar ana nedenler
JVM tuning'de önce ölç, sonra ayarla.
-Xms,-Xmx, GC logları temel araçların
AI Asistan
Sorularını yanıtlamaya hazır