Java uygulamalarında performans, çoğu zaman “kod hızlı mı?” sorusundan daha geniş bir alana yayılır. Üretimde karşılaşılan yavaşlamalar; GC (Garbage Collection) davranışı, JVM ayarları, bellek baskısı, thread planlaması, yanlış ölçümleme ve hatalı kapasite varsayımları gibi sebeplerle ortaya çıkar. Bu yazı, Java’da Performans İpuçları: GC, JVM Ayarları ve Best Practices konusunu pratik ve uygulanabilir şekilde ele alır. İlk hedef, “Nedir?” sorusunu netleştirmek; ardından “Nasıl yapılır?”, “En iyi yöntem hangisi?”, “2026 güncel yöntem var mı?” ve “Hata alırsam ne yapmalıyım?” sorularına adım adım cevap vermektir.
Bu rehber Datalife.Dev içerik standardına göre hazırlanmıştır. Bilişim sektöründe çalışan geliştiriciler, webmaster’lar, web site yöneticileri ve web yazılım kullanıcıları için; GC, JVM tuning ve performans best practices başlıklarını “kopya gibi durmayan”, ölçülebilir çıktılara odaklanan bir formatta sunar. Buradaki yaklaşım: önce ölç, sonra değiştir; değiştirince tekrar ölç.
Java Performansı Nedir? (Kapsamı Doğru Tanımlama)
Java performansı tek bir metrik değildir. Genellikle şu hedeflerden biri veya birkaçı önemlidir:
-
Gecikme (latency): İstek yanıt süresi (p95/p99)
-
Throughput: Saniyedeki istek / işlenen mesaj sayısı
-
Kaynak kullanımı: CPU, heap, off-heap, disk, ağ
-
Stabilite: Uzun süreli çalışma, bellek sızıntısı olmaması
-
Maliyet: Aynı yükü daha az sunucu ile taşıma
İpucu: “Uygulama yavaş” demek yerine hangi metrik bozuldu? sorusuna cevap verin: p99 mu yükseldi, CPU mu arttı, GC duraklamaları mı uzadı?
Java’da performans tartışmalarının büyük bölümü GC duraklamaları, heap boyutlandırma, thread blokajı ve IO beklemeleri etrafında döner. Bu nedenle bu yazının ağırlığı GC ve JVM ayarlarında olacaktır.
GC (Garbage Collection) Nedir ve Neden Yavaşlatır?
GC, Java’da artık kullanılmayan nesneleri otomatik temizleyen mekanizmadır. Bu konfor, yanlış nesne üretimi veya yanlış heap ayarlarıyla birleşince “duraklama” (pause) ve “jitter” üretebilir.
GC’nin performansa etkisi nasıl oluşur?
-
Çok kısa ömürlü nesne üretimi artarsa Young GC sıklaşır.
-
Heap dolmaya yaklaşırsa Major/Old GC devreye girebilir.
-
Bazı collector’lar throughput’u artırırken latency’yi bozabilir (veya tam tersi).
Dikkat: GC, “kötü” olduğu için değil; sistemin ürettiği çöp (allocation rate) ve ayarlar uyumsuz olduğu için sorun çıkarır.
Temel GC terimleri (kısa sözlük)
-
Allocation rate: Saniyede yaratılan nesne miktarı
-
Pause time: GC sırasında uygulamanın durması
-
Throughput: Uygulamanın iş yapabildiği süre oranı
-
STW (Stop-The-World): Bazı safhalarda tüm thread’lerin durması
-
Promotion: Nesnenin young’dan old’a taşınması
2026 Güncel Yaklaşım: Hangi GC, Hangi Senaryoda?
2026’da Java ekosisteminde GC seçimi hâlâ iş yüküne göre değişir. “Tek doğru GC” yerine “hedef metrik” yaklaşımı daha sağlıklıdır.
Yaygın GC seçenekleri ve pratik kullanım alanları
Aşağıdaki tablo karar vermeyi kolaylaştırır:
| Hedef | GC Eğilimi | Ne zaman mantıklı? | Risk / dikkat |
|---|---|---|---|
| Düşük gecikme (p99) | G1 / ZGC / Shenandoah | API, finans, interaktif sistemler | Yanlış heap ile beklenmedik dalgalanma |
| Yüksek throughput | G1 / Parallel GC | Batch, ETL, yoğun CPU işleri | Latency dalgalanabilir |
| Basitlik / stabil varsayımlar | G1 | Çoğu web servisi | Default’lara güvenip ölçüm yapmamak |
İpucu: Çoğu kurumsal serviste ilk aşamada G1 ile doğru heap ve doğru ölçümleme, “collector değiştirmekten” daha hızlı sonuç verir.
Sık hata: “GC’yi değiştirince düzelir” refleksi. Asıl sorun bazen nesne üretimi veya yanlış cache stratejisi olabilir.
JVM Ayarları Nedir? Nereden Başlanmalı?
JVM ayarları, uygulamanın bellek yönetimi ve çalışma karakterini belirler. “Her yerde aynı flags” yaklaşımı yerine, aşağıdaki sırayı izlemek genellikle daha iyi sonuç verir.
1) En kritik: Heap boyutu ve sınırları
-
Xms: Başlangıç heap
-
Xmx: Maksimum heap
Genellikle şu yaklaşım kullanılır:
-
Üretimde Xms = Xmx yapmak, heap’in büyüme küçülme maliyetini azaltır ve daha stabil davranış verir.
-
Çok büyük heap, bazı durumlarda GC pause sürelerini uzatabilir.
Dikkat: Xmx’i “ne kadar büyük o kadar iyi” diye artırmak, GC’nin temizleyeceği alanı büyütür. Amaç: gereğinden büyük değil, ihtiyaca uygun heap.
2) GC seçimi ve hedefleri
Collector seçimi yaptıktan sonra hedefler belirlenir. Örneğin düşük gecikme istiyorsanız, pause hedefi üzerinden ilerlenir.
3) Loglama ve gözlemlenebilirlik
JVM ayarlarını değiştirmenin anlamı, etkiyi ölçebildiğinizde ortaya çıkar.
Önerilen kontrol listesi:
-
p95/p99 latency ölçülüyor mu?
-
GC duraklamaları ve frekansı görülebiliyor mu?
-
CPU ve heap trendi izleniyor mu?
-
Deploy sonrası metrik kıyaslaması yapılıyor mu?
Bu rehber Datalife.Dev içerik standardına göre hazırlanmıştır: ayar değişikliği tek başına “iyileştirme” sayılmaz; ölçüm ve karşılaştırma şarttır.
“Nasıl Yapılır?”: Adım Adım Performans İyileştirme Süreci
Adım 1: Sorunu sınıflandır
Önce sorunun tipi belirlenir:
-
Latency artışı (p99 fırlıyor)
-
CPU artışı (işlemci sürekli yüksek)
-
OOM / bellek baskısı (OutOfMemoryError, container kill)
-
GC baskısı (sık GC, uzun duraklama)
-
IO beklemesi (DB, disk, network)
Adım 2: Minimum tekrarlanabilir test oluştur
-
Aynı trafik profili / aynı veri hacmi
-
Aynı JVM sürümü ve aynı container limitleri
-
Aynı konfigürasyon
Sık hata: Üretimdeki sorunu lokal testle kanıtlamaya çalışmak. Üretim verisi ve trafik deseni farklıysa sonuç yanıltır.
Adım 3: “Önce kod mu, önce JVM mi?”
Genellikle şu sıralama tercih edilir:
-
Aşırı nesne üretimini azalt
-
En büyük hot-path’leri optimize et
-
Heap ve GC ayarlarını hedef metriğe göre düzenle
-
Thread/connection pool ayarlarını düzelt
-
Yük testini tekrar et
Best Practices: Kod Tarafında En Çok Kazandıran İyileştirmeler
Nesne üretimini kontrol et (allocation rate)
GC’nin en büyük tetikleyicisi genellikle gereksiz nesne üretimidir.
-
String birleştirmelerinde gereksiz ara nesnelerden kaçın
-
Büyük koleksiyonlarda kapasiteyi önden belirlemeyi düşün
-
Gereksiz boxing/unboxing durumlarını azalt
-
Aynı format/regex işlemlerini tekrar tekrar üretme (yeniden kullan)
İpucu: “Daha hızlı algoritma” kadar “daha az allocation” da fark yaratır.
Koleksiyon ve cache stratejileri
-
Cache boyutunu sınırsız bırakmak bellek baskısı üretir.
-
TTL/limit politikaları belirlemek stabiliteyi artırır.
Dikkat: Cache “performans için” eklenir; ama sınırsız cache çoğu zaman “OOM için” sebep olur.
Exception kullanımını doğru konumlandır
Exception, kontrol akışı için sürekli kullanılırsa pahalı olabilir.
-
Hata istisna olmalı; normal akış olmamalı.
-
Sık hataya düşen “validasyon” logic’ini exception yerine koşul ile yönetmek tercih edilebilir.
IO ve DB erişiminde tıkanmayı azalt
-
DB sorgularında doğru index ve doğru limit
-
Connection pool kapasitesi: “fazla açmak” da sorundur
-
Timeout değerlerini gerçekçi ayarla (sonsuz timeout risklidir)
Best Practices: JVM ve Çalışma Ortamı Tarafı
Container (Docker/Kubernetes) gerçekleri
Güncel üretim ortamlarında en sık sorunlardan biri: JVM’in bellek limitlerini container’dan bağımsız varsayması.
Önerilen yaklaşım:
-
Container memory limitine göre heap’i planla
-
Sadece heap değil: metaspace, thread stack, direct buffer gibi alanları da hesaba kat
Dikkat: “Xmx = container memory” yapmak risklidir. Heap dışı bellek tüketimi nedeniyle OOM-kill görülebilir.
Thread ve pool yönetimi
-
Web uygulamalarında thread pool, throughput ve latency için kritik olabilir.
-
Çok fazla thread, context switch maliyeti ve bellek tüketimi getirir.
-
Çok az thread, kuyruklanma ve latency artışı üretir.
İpucu: Thread pool ayarı tek başına değil; DB pool, HTTP client pool ve queue ayarlarıyla birlikte ele alınır.
Pratik JVM Ayarı Şablonları (Senaryoya Göre)
Aşağıdaki örnekler “kopyala-yapıştır” reçetesi değil; başlangıç noktasıdır. Üretimde mutlaka ölçümle doğrulanmalıdır.
Web API (latency hassas)
-
Heap stabil (Xms=Xmx)
-
GC duraklamaları izleniyor
-
Timeout’lar net
Batch/ETL (throughput odaklı)
-
Daha büyük heap tolere edilebilir
-
GC pause kritik değilse throughput önceliklendirilir
-
IO ve disk throughput’u izlenir
Sık hata: Batch işlerinde “aynı JVM ayarını” API servisine uygulamak (veya tam tersi). Hedef metrik değişince ayar mantığı da değişir.
GC Kaynaklı Tipik Hatalar ve Ne Yapmalı?
1) “Latency dalgalanıyor, arada donuyor”
Muhtemel nedenler:
-
Uzun GC pause
-
Thread pool kuyruklanması
-
DB beklemeleri
Ne yapılır?
-
p95/p99 trendine bak
-
GC pause süreleri ile aynı zamanda mı artıyor kontrol et
-
Allocation rate yüksekse hot-path’te nesne üretimini azalt
-
Heap çok küçükse sık GC’ye girer; çok büyükse uzun pause üretebilir—dengeyi ölçerek bul
2) OutOfMemoryError (OOM)
Muhtemel nedenler:
-
Sınırsız cache / koleksiyon büyümesi
-
Memory leak (referansların tutulması)
-
Heap dışı bellek tüketimi (direct buffer, metaspace, thread stack)
Ne yapılır?
-
Hızlı kontrol: deploy sonrası mı başladı, trafik mi arttı?
-
Cache limit/TTL ekle
-
Büyük objeleri request scope dışında tutmama
-
Heap dışı tüketimi hesaba katarak container limitlerini düzenle
Dikkat: OOM’u sadece “Xmx artırarak” geçiştirmek, kök nedeni maskeleyebilir.
3) “CPU sürekli yüksek”
Muhtemel nedenler:
-
Sürekli GC döngüsü (GC thrashing)
-
Sık çalışan pahalı iş (regex, JSON parse, şifreleme)
-
Lock contention (synchronization)
Ne yapılır?
-
GC ve CPU korelasyonuna bak
-
Hot method’ları tespit edip gereksiz tekrarları azalt
-
Senkron blokları daralt, concurrent yapıların doğru kullanımını gözden geçir
En İyi Yöntem Hangisi? (Özet Karar Çerçevesi)
Performans iyileştirmede “en iyi yöntem”, hedefe göre değişir. Ancak pratikte en sağlam yöntem şu çerçevedir:
-
Ölç: p95/p99, CPU, heap, GC pause
-
Sınıflandır: sorun GC mi, IO mu, CPU mu?
-
Önce basit kazançlar: allocation azalt, cache sınırla, query optimize et
-
Sonra JVM tuning: heap ve GC hedeflerini metrikle uyumla
-
Doğrula: aynı testle kıyasla
Bu rehber Datalife.Dev içerik standardına göre hazırlanmıştır; hedef, “ayar değiştirdim” değil “ölçtüm, iyileşti” demektir.
Sonuç: Java Performansı Bir Süreçtir, Bayrak Yarışı Gibi Yönetilir 🧠
Java’da performans; GC, JVM ve kod pratiklerinin birlikte çalışmasıyla oluşur. 2026’da da değişmeyen temel: ölçmeden tuning yapılmaması, hedef metriğe göre karar verilmesi ve iyileştirmelerin tekrarlanabilir testlerle doğrulanmasıdır. Eğer bugün bir yerden başlayacaksanız; önce p95/p99 gecikme ve GC duraklama trendlerini görünür hale getirip, hot-path’teki gereksiz nesne üretimini azaltmayı deneyin. Bu yaklaşım çoğu sistemde hızlı ve kalıcı fayda sağlar; ardından JVM ayarlarını veriye dayanarak şekillendirmek daha güvenli olur.

