<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ali Karahisar</title>
	<atom:link href="https://alikarahisar.com/blog/feed/" rel="self" type="application/rss+xml" />
	<link>https://alikarahisar.com/blog/</link>
	<description>Yazılım ve AI Hakkında Karalamalar</description>
	<lastBuildDate>Fri, 11 Sep 2026 09:04:24 +0000</lastBuildDate>
	<language>tr</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://alikarahisar.com/blog/wp-content/uploads/2026/08/cropped-Code_Generated_Image-32x32.png</url>
	<title>Ali Karahisar</title>
	<link>https://alikarahisar.com/blog/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Spring Cloud vs. Kubernetes: Service Discovery ve Trafik Yönetimi Kime Emanet Edilmeli?</title>
		<link>https://alikarahisar.com/blog/spring-cloud-vs-kubernetes-service-discovery-trafik-yonetimi/</link>
					<comments>https://alikarahisar.com/blog/spring-cloud-vs-kubernetes-service-discovery-trafik-yonetimi/#respond</comments>
		
		<dc:creator><![CDATA[Ali Karahisar]]></dc:creator>
		<pubDate>Fri, 11 Sep 2026 08:59:21 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Mikroservices]]></category>
		<category><![CDATA[Yazılım]]></category>
		<category><![CDATA[Yazılım Mimarisi]]></category>
		<category><![CDATA[Consul vs Kubernetes]]></category>
		<category><![CDATA[hizmet keşfi]]></category>
		<category><![CDATA[Istio trafik yönetimi]]></category>
		<category><![CDATA[Kubernetes ConfigMap]]></category>
		<category><![CDATA[Kubernetes DNS]]></category>
		<category><![CDATA[Kubernetes service discovery]]></category>
		<category><![CDATA[mikroservis yük dengeleme]]></category>
		<category><![CDATA[service discovery]]></category>
		<category><![CDATA[Spring Cloud Eureka]]></category>
		<category><![CDATA[Spring Cloud LoadBalancer]]></category>
		<guid isPermaLink="false">https://alikarahisar.com/blog/?p=58</guid>

					<description><![CDATA[<p>Mikroservis mimarisinde “Spring Cloud mu Kubernetes mi?” sorusu sıkça gündeme gelir. Ancak bir sistem mimarı açısından asıl karar, kullanılacak ürünlerden önce sorumluluk sınırlarıyla ilgilidir: Hizmet keşfi, yük dengeleme ve konfigürasyon yönetiminin hangi bölümü uygulamaya, hangi bölümü platforma ait olmalıdır? Spring Cloud, Eureka ve Consul gibi çözümler bu sorumlulukları uygulama seviyesine yaklaştırır. Kubernetes DNS ve Kubernetes [&#8230;]</p>
<p><a href="https://alikarahisar.com/blog/spring-cloud-vs-kubernetes-service-discovery-trafik-yonetimi/">Spring Cloud vs. Kubernetes: Service Discovery ve Trafik Yönetimi Kime Emanet Edilmeli?</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Mikroservis mimarisinde “Spring Cloud mu Kubernetes mi?” sorusu sıkça gündeme gelir. Ancak bir sistem mimarı açısından asıl karar, kullanılacak ürünlerden önce sorumluluk sınırlarıyla ilgilidir:</p>



<span id="more-58"></span>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Hizmet keşfi, yük dengeleme ve konfigürasyon yönetiminin hangi bölümü uygulamaya, hangi bölümü platforma ait olmalıdır?</p>
</blockquote>



<p class="wp-block-paragraph">Spring Cloud, Eureka ve Consul gibi çözümler bu sorumlulukları uygulama seviyesine yaklaştırır. Kubernetes DNS ve Kubernetes Service temel ağ yeteneklerini altyapı seviyesinde sunar. Istio gibi Service Mesh ürünleri ise merkezi trafik politikaları, gözlemlenebilirlik ve servisler arası güvenlik katmanı ekler.</p>



<p class="wp-block-paragraph">Doğru seçim yalnızca servis sayısına bağlı değildir. Ekip sayısı, kullanılan programlama dilleri, dağıtım ortamı, güvenlik gereksinimleri ve organizasyonun platform işletme kapasitesi birlikte değerlendirilmelidir.</p>



<h2 class="wp-block-heading">Kısa cevap: Hangi sorumluluk kime verilmeli?</h2>



<p class="wp-block-paragraph">Kubernetes üzerinde çalışan yeni sistemler için genel mimari öneri şöyledir:</p>



<ul class="wp-block-list">
<li>Hizmet keşfi: Kubernetes Service ve DNS</li>



<li>Temel yük dengeleme: Kubernetes ağ katmanı</li>



<li>Gelişmiş L7 trafik politikaları: İhtiyaç varsa Istio</li>



<li>İş anlamı taşıyan retry, fallback ve idempotency: Uygulama</li>



<li>Dağıtım konfigürasyonu: ConfigMap ve GitOps</li>



<li>Dinamik uygulama konfigürasyonu: Gerekiyorsa Spring Cloud Config veya Consul</li>



<li>Hassas bilgiler: KMS veya harici secret manager</li>
</ul>



<p class="wp-block-paragraph">Kubernetes dışında çalışan veya VM, container ve farklı veri merkezlerini birlikte kullanan hibrit sistemlerde Consul güçlü bir alternatiftir. Tamamen Java tabanlı daha küçük yapılarda Eureka da yeterli olabilir.</p>



<p class="wp-block-paragraph">Aynı sorumluluğu hem Spring Cloud hem Kubernetes tarafında kalıcı olarak çözmek ise genellikle gereksiz karmaşıklık yaratır.</p>



<h2 class="wp-block-heading">Service Discovery nedir?</h2>



<p class="wp-block-paragraph">Service discovery, bir servisin iletişim kuracağı diğer servislerin güncel ağ adreslerini bulmasını sağlar.</p>



<p class="wp-block-paragraph">Mikroservis instance’ları ölçekleme, yeniden başlatma veya hata nedeniyle sürekli değişebilir. Bu nedenle uygulamaların sabit IP adreslerine bağlanması sürdürülebilir değildir. Bir servis kataloğu veya kararlı platform adresi gerekir.</p>



<h3 class="wp-block-heading">Spring Cloud Eureka ile hizmet keşfi</h3>



<p class="wp-block-paragraph">Eureka modelinde uygulamalar açılırken kendilerini merkezi registry’ye kaydeder ve düzenli heartbeat gönderir. İstemciler, çağırmak istedikleri servisin instance listesini Eureka’dan alır.</p>



<p class="wp-block-paragraph">Güncel Spring Cloud mimarisinde Eureka hizmet keşfini sağlarken istemci tarafı yük dengeleme Spring Cloud LoadBalancer tarafından gerçekleştirilir. Varsayılan seçim round-robin’dir; farklı seçim stratejileri de uygulanabilir. <a href="https://docs.spring.io/spring-cloud-netflix/reference/spring-cloud-netflix.html">Spring Cloud Netflix</a>, <a href="https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/loadbalancer.html">Spring Cloud LoadBalancer</a></p>



<p class="wp-block-paragraph">Bu yaklaşım özellikle şu koşullarda anlamlıdır:</p>



<ul class="wp-block-list">
<li>Sistem ağırlıklı olarak Spring Boot kullanıyorsa</li>



<li>Servisler VM veya bare metal üzerinde çalışıyorsa</li>



<li>Kubernetes’e geçiş henüz tamamlanmadıysa</li>



<li>Uygulama seviyesinde instance metadata’sına göre seçim gerekiyorsa</li>
</ul>



<p class="wp-block-paragraph">Eureka’nın temel dezavantajı, discovery bilgisini uygulama çalışma zamanının bir parçası hâline getirmesidir. Her servis registry istemcisi taşır; kütüphane sürümleri ve cache davranışları sistemin ağ davranışını etkiler.</p>



<h3 class="wp-block-heading">Consul ile hizmet keşfi</h3>



<p class="wp-block-paragraph">Consul; servis kataloğu, sağlık kontrolleri, HTTP API ve DNS tabanlı keşif sunar. Spring Cloud Consul kullanan uygulamalar HTTP API üzerinden kayıt olabilirken Java dışındaki uygulamalar aynı kataloğa DNS aracılığıyla erişebilir. <a href="https://docs.spring.io/spring-cloud-consul/reference/discovery.html">Spring Cloud Consul</a></p>



<p class="wp-block-paragraph">Consul’un Eureka’ya göre en önemli avantajı platform ve dil bağımsızlığıdır. VM, container, Kubernetes ve farklı veri merkezlerinin birlikte bulunduğu yapılarda ortak bir servis kataloğu oluşturabilir.</p>



<p class="wp-block-paragraph">Bunun karşılığında Consul cluster’ının yüksek erişilebilir biçimde kurulması, izlenmesi, yükseltilmesi ve güvenliğinin sağlanması gerekir.</p>



<h3 class="wp-block-heading">Kubernetes Service Discovery</h3>



<p class="wp-block-paragraph">Kubernetes’te Pod IP adresleri geçicidir. Uygulamaların bağlandığı kararlı kimlik <code>Service</code> nesnesidir.</p>



<p class="wp-block-paragraph">CoreDNS, Kubernetes servisleri için DNS kayıtları oluşturur. Kubernetes de bir servise bağlı güncel Pod listesini EndpointSlice nesneleri üzerinden takip eder. Böylece uygulama <code>http://orders</code> gibi kararlı bir servis adına bağlanabilir. Registry SDK’sı, heartbeat veya istemci tarafında instance listesi yönetmesi gerekmez. <a href="https://kubernetes.io/docs/concepts/services-networking/service/">Kubernetes Service</a>, <a href="https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/">Kubernetes DNS</a></p>



<p class="wp-block-paragraph">Bu modelin başlıca avantajları şunlardır:</p>



<ul class="wp-block-list">
<li>Programlama dilinden bağımsızdır.</li>



<li>Uygulamayı discovery kodundan arındırır.</li>



<li>Pod yaşam döngüsü ile endpoint yönetimini aynı kontrol düzlemine bağlar.</li>



<li>Platform genelinde standart servis adresleme modeli oluşturur.</li>



<li>Kubernetes kullanılıyorsa ek registry işletme ihtiyacını ortadan kaldırır.</li>
</ul>



<p class="wp-block-paragraph">Bu nedenle Kubernetes üzerinde çalışan yeni bir sistemde yalnızca hizmet keşfi için Eureka kurmak çoğunlukla gereksizdir.</p>



<h2 class="wp-block-heading">Spring Cloud LoadBalancer ve Kubernetes Service farkı</h2>



<p class="wp-block-paragraph">Spring Cloud LoadBalancer ile Kubernetes Service aynı sorunu farklı katmanlarda çözer.</p>



<h3 class="wp-block-heading">İstemci tarafı yük dengeleme</h3>



<p class="wp-block-paragraph">Spring Cloud LoadBalancer modelinde uygulama:</p>



<ol start="1" class="wp-block-list">
<li>Discovery sisteminden instance listesini alır.</li>



<li>Uygun instance’ı seçer.</li>



<li>İsteği doğrudan o instance’a gönderir.</li>
</ol>



<p class="wp-block-paragraph">Bu model, istemcinin metadata, bölge veya özel iş bağlamına göre hedef seçmesine olanak tanır. Buna karşılık seçim mantığı her uygulamanın içine dağılır.</p>



<h3 class="wp-block-heading">Altyapı tarafı yük dengeleme</h3>



<p class="wp-block-paragraph">Kubernetes Service modelinde uygulama tek bir servis adresine bağlanır. Trafiğin uygun Pod’a aktarılması kube-proxy veya kullanılan ağ altyapısı tarafından gerçekleştirilir.</p>



<p class="wp-block-paragraph">Bu yaklaşım uygulamayı sadeleştirir ve farklı dillerde yazılmış servisler arasında standart davranış sağlar. Ancak temel Kubernetes Service; HTTP header’ına göre yönlendirme, yüzdesel canary veya request-level circuit breaking gibi gelişmiş L7 politikaları sunmaz.</p>



<p class="wp-block-paragraph">Ayrıca uzun süre açık kalan HTTP/2 veya gRPC bağlantıları dikkatle değerlendirilmelidir. DNS ya da endpoint kümesi değişse bile connection pool aynı backend’e trafik göndermeyi sürdürebilir.</p>



<h2 class="wp-block-heading">Istio trafik yönetimine ne ekler?</h2>



<p class="wp-block-paragraph">Istio, Kubernetes service discovery mekanizmasının yerine geçmez. Kubernetes servis ve endpoint bilgisini kullanır; bunun üzerine programlanabilir bir trafik katmanı ekler.</p>



<p class="wp-block-paragraph">Istio ile merkezi olarak şu politikalar uygulanabilir:</p>



<ul class="wp-block-list">
<li>Ağırlıklı ve sürüm bazlı yönlendirme</li>



<li>Canary ve aşamalı dağıtım</li>



<li>HTTP header veya path bazlı routing</li>



<li>Timeout ve retry</li>



<li>Circuit breaking</li>



<li>Sağlıksız instance’ları havuzdan çıkarma</li>



<li>Trafik yansıtma</li>



<li>Servisler arası mTLS</li>



<li>Kimlik tabanlı erişim politikaları</li>



<li>Ortak trafik metriği ve erişim kayıtları</li>
</ul>



<p class="wp-block-paragraph">Istio’nun trafik modeli Envoy proxy’leri kullanır. Sidecar modelinde her Pod’un yanında bir proxy çalışır. Ambient modelde temel L4 işlevleri node seviyesinde sağlanabilir; L7 özellikleri gerektiğinde waypoint proxy devreye girer. <a href="https://istio.io/latest/docs/concepts/traffic-management/">Istio Traffic Management</a>, <a href="https://istio.io/latest/docs/overview/dataplane-modes/">Istio veri düzlemi modelleri</a></p>



<h3 class="wp-block-heading">Istio’nun avantajları</h3>



<ul class="wp-block-list">
<li>Farklı dillerdeki servisler için ortak ağ politikası sağlar.</li>



<li>Trafik davranışı uygulama sürümünden bağımsız değiştirilebilir.</li>



<li>Canary ve progressive delivery süreçlerini kolaylaştırır.</li>



<li>mTLS ve servis kimliğini merkezi hâle getirir.</li>



<li>Gözlemlenebilirlik standardı oluşturur.</li>



<li>Politika yönetimini platform ekibinde toplar.</li>
</ul>



<h3 class="wp-block-heading">Istio’nun maliyetleri</h3>



<ul class="wp-block-list">
<li>Yeni bir kontrol ve veri düzlemi işletilir.</li>



<li>Proxy’ler CPU, bellek ve belirli ölçüde gecikme ekler.</li>



<li>Hata ayıklama süreci daha karmaşık hâle gelir.</li>



<li>Yanlış bir merkezi politika çok sayıda servisi etkileyebilir.</li>



<li>Proxy ve kontrol düzlemi yükseltmeleri dikkatle yönetilmelidir.</li>



<li>Platform ekibinde Service Mesh uzmanlığı gerekir.</li>
</ul>



<p class="wp-block-paragraph">Dolayısıyla Istio, “mikroservis kullanıyoruz” gerekçesiyle kurulacak bir bileşen değildir. Sağladığı merkezi politika değeri, işletme maliyetini geçtiğinde kullanılmalıdır.</p>



<h2 class="wp-block-heading">Konfigürasyon yönetimi: Spring Cloud Config mi ConfigMap mi?</h2>



<p class="wp-block-paragraph">Konfigürasyon yönetimi, service discovery ve trafik yönetiminden ayrı ele alınmalıdır. Istio uygulamanın iş konfigürasyonunu yönetmez.</p>



<h3 class="wp-block-heading">Spring Cloud Config</h3>



<p class="wp-block-paragraph">Spring Cloud Config, uygulama ayarlarını merkezi bir servis üzerinden sunar. Git deposu kullanıldığında değişiklik geçmişi, sürümleme ve ortam bazlı konfigürasyon yönetimi elde edilir.</p>



<p class="wp-block-paragraph">Şu durumlarda güçlüdür:</p>



<ul class="wp-block-list">
<li>Uygulamalar Kubernetes dışında da çalışıyorsa</li>



<li>Spring profile modeli yoğun kullanılıyorsa</li>



<li>Uygulama ve ortam bazlı konfigürasyon hiyerarşisi gerekiyorsa</li>



<li>Konfigürasyonun deployment’tan bağımsız yenilenmesi isteniyorsa</li>
</ul>



<p class="wp-block-paragraph">Dezavantajı, Config Server’ın uygulama başlangıcında ek bir bağımlılığa dönüşmesidir. Dinamik refresh sırasında bütün bean’lerin ve bağlantı havuzlarının yeni değerlerle güvenli biçimde çalışacağı da garanti değildir.</p>



<h3 class="wp-block-heading">Consul Config</h3>



<p class="wp-block-paragraph">Spring Cloud Consul Config, Consul KV üzerinden uygulama ayarlarını yükleyebilir. Config Watch, ilgili değerlerde değişiklik algılandığında Spring refresh olayı oluşturabilir. <a href="https://docs.spring.io/spring-cloud-consul/reference/config.html">Spring Cloud Consul Config</a></p>



<p class="wp-block-paragraph">Discovery için zaten Consul kullanılan hibrit sistemlerde operasyonel açıdan çekici olabilir. Ancak aynı Consul cluster’ının hem discovery hem konfigürasyon için kullanılması hata alanını büyütür. Consul kesintisinin iki kritik fonksiyonu birden etkilemesi hesaba katılmalıdır.</p>



<h3 class="wp-block-heading">Kubernetes ConfigMap</h3>



<p class="wp-block-paragraph">Kubernetes ConfigMap, hassas olmayan uygulama ayarlarını container imajından ayırır. Değerler ortam değişkeni veya volume içindeki dosyalar olarak Pod’a aktarılabilir.</p>



<p class="wp-block-paragraph">Volume olarak bağlanan ConfigMap güncellemeleri zaman içinde Pod’a yansıtılabilir; fakat uygulamanın dosyayı yeniden okuması gerekir. Ortam değişkeni olarak verilen ConfigMap değerleri otomatik güncellenmez ve Pod yeniden başlatılmalıdır. <a href="https://kubernetes.io/docs/concepts/configuration/configmap/">Kubernetes ConfigMap</a></p>



<p class="wp-block-paragraph">Birçok üretim sistemi için kontrollü rolling restart, dinamik context refresh’ten daha güvenlidir. Yeni konfigürasyon yeni Pod’larda doğrulanır; readiness kontrolü başarısız olursa rollout durdurulabilir.</p>



<h3 class="wp-block-heading">Kubernetes Secret yeterli mi?</h3>



<p class="wp-block-paragraph">Kubernetes Secret nesnesi kullanmak tek başına güvenli secret yönetimi anlamına gelmez. Secret verileri varsayılan olarak etcd içinde şifrelenmeden saklanabilir.</p>



<p class="wp-block-paragraph">Üretim ortamında şu kontroller gerekir:</p>



<ul class="wp-block-list">
<li>Encryption-at-rest</li>



<li>Sıkı RBAC</li>



<li>Secret erişim kayıtları</li>



<li>Düzenli rotasyon</li>



<li>Tercihen KMS veya harici secret manager entegrasyonu</li>
</ul>



<p class="wp-block-paragraph"><a href="https://kubernetes.io/docs/concepts/security/secrets-good-practices/">Kubernetes Secret güvenlik önerileri</a></p>



<h2 class="wp-block-heading">Retry ve circuit breaker uygulamada mı mesh’te mi olmalı?</h2>



<p class="wp-block-paragraph">Bu sorumluluk bütünüyle tek bir katmana verilemez.</p>



<p class="wp-block-paragraph">Mesh şu teknik kararları üstlenebilir:</p>



<ul class="wp-block-list">
<li>Bağlantı kurulamazsa farklı endpoint denemek</li>



<li>Belirli hata kodlarında sınırlı retry yapmak</li>



<li>Sağlıksız instance’ı geçici olarak havuzdan çıkarmak</li>



<li>Ağ seviyesinde timeout standardı uygulamak</li>
</ul>



<p class="wp-block-paragraph">Uygulamada kalması gereken kararlar ise şunlardır:</p>



<ul class="wp-block-list">
<li>Bir ödeme isteği güvenle tekrar edilebilir mi?</li>



<li>İşlem idempotent mi?</li>



<li>Hangi fallback iş açısından kabul edilebilir?</li>



<li>Kısmi başarı nasıl yönetilmeli?</li>



<li>Telafi işlemi gerekiyor mu?</li>
</ul>



<p class="wp-block-paragraph">Mesh, bir HTTP isteğinin teknik olarak tekrar edilebilir göründüğünü anlayabilir; fakat tekrarın ikinci kez tahsilat yaratıp yaratmayacağını bilemez.</p>



<p class="wp-block-paragraph">Ayrıca uygulama ve mesh bağımsız retry yaptığında deneme sayıları çarpılır. Uygulamadaki üç deneme ile mesh’teki üç deneme, tek kullanıcı isteğini dokuz downstream çağrısına dönüştürebilir. Retry bütçesi uçtan uca tasarlanmalıdır.</p>



<h2 class="wp-block-heading">Proje ölçeğine göre mimari tercih</h2>



<p class="wp-block-paragraph">Servis sayıları kesin sınır değildir. Ekip sayısı, teknoloji çeşitliliği, güvenlik gereksinimi ve dağıtım sıklığı daha belirleyicidir.</p>



<h3 class="wp-block-heading">Küçük projeler: 1–2 ekip ve yaklaşık 3–10 servis</h3>



<p class="wp-block-paragraph">Kubernetes üzerinde çalışılıyorsa:</p>



<ul class="wp-block-list">
<li>Kubernetes DNS ve Service kullanın.</li>



<li>ConfigMap ve kontrollü rolling deployment tercih edin.</li>



<li>Istio kurmayın.</li>



<li>Timeout, idempotency ve iş fallback’lerini uygulamada yönetin.</li>
</ul>



<p class="wp-block-paragraph">Bu ölçekte Service Mesh’in işletme ve öğrenme maliyeti genellikle faydasından büyüktür.</p>



<p class="wp-block-paragraph">VM veya karma altyapıda çalışılıyorsa:</p>



<ul class="wp-block-list">
<li>Tamamen Java tabanlı sistemlerde Eureka kullanılabilir.</li>



<li>Birden fazla dil veya çalışma ortamı varsa Consul tercih edilebilir.</li>



<li>Discovery ihtiyacı tek başına Kubernetes’e geçiş gerekçesi olmamalıdır.</li>
</ul>



<h3 class="wp-block-heading">Orta ölçekli projeler: 3–8 ekip ve yaklaşık 10–50 servis</h3>



<p class="wp-block-paragraph">Kubernetes’i varsayılan discovery ve temel load-balancing katmanı yapın. Yeni servislerin ayrıca Eureka’ya kaydolmasını istemeyin.</p>



<p class="wp-block-paragraph">Istio’yu şu ihtiyaçlar somutlaştığında değerlendirin:</p>



<ul class="wp-block-list">
<li>Takımlar arasındaki retry ve timeout farklılıkları üretim olaylarına yol açıyorsa</li>



<li>Merkezi canary dağıtımı gerekiyorsa</li>



<li>Servisler arası mTLS zorunluysa</li>



<li>Polyglot yapı ortak ağ telemetrisi gerektiriyorsa</li>



<li>Merkezi erişim politikaları yönetilecekse</li>
</ul>



<p class="wp-block-paragraph">Mesh’e geçiş kritik servis veya namespace’lerle kademeli olarak başlatılabilir.</p>



<h3 class="wp-block-heading">Büyük projeler: 8’den fazla ekip, 50+ servis veya çoklu cluster</h3>



<p class="wp-block-paragraph">Bu ölçekte her uygulama ekibinin aynı ağ politikalarını farklı kütüphanelerle uygulaması sürdürülemez hâle gelebilir.</p>



<p class="wp-block-paragraph">Önerilen sorumluluk dağılımı:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><th>Sorumluluk</th><th>Önerilen sahip</th></tr><tr><td>Hizmet keşfi</td><td>Kubernetes Service ve DNS</td></tr><tr><td>Endpoint yaşam döngüsü</td><td>Kubernetes readiness ve EndpointSlice</td></tr><tr><td>Temel yük dengeleme</td><td>Kubernetes ağ katmanı</td></tr><tr><td>Gelişmiş trafik yönetimi</td><td>Istio</td></tr><tr><td>Servis kimliği ve mTLS</td><td>Istio</td></tr><tr><td>İş seviyesinde retry ve fallback</td><td>Uygulama</td></tr><tr><td>Dağıtım konfigürasyonu</td><td>GitOps ve ConfigMap</td></tr><tr><td>Hassas bilgiler</td><td>KMS veya harici secret manager</td></tr><tr><td>Platform standartları</td><td>Platform ekibi</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Mesh’in anlamlı olmasını sağlayan yalnızca servis sayısı değildir. Merkezi güvenlik, çoklu dil desteği, yüksek dağıtım sıklığı ve çapraz takım standardizasyonu operasyon maliyetini karşılayacak düzeye ulaşmalıdır.</p>



<h2 class="wp-block-heading">Kaçınılması gereken yaklaşım: Çift sahiplik</h2>



<p class="wp-block-paragraph">Geçiş dönemi dışında şu kombinasyonlardan kaçınılmalıdır:</p>



<ul class="wp-block-list">
<li>Eureka ve Kubernetes’in paralel servis kataloğu olması</li>



<li>Spring Cloud LoadBalancer ile Istio’nun bağımsız hedef seçmesi</li>



<li>Uygulama, API gateway ve mesh’in ayrı ayrı retry uygulaması</li>



<li>Timeout değerlerinin farklı katmanlarda çelişmesi</li>



<li>Aynı ayarın Consul KV ve ConfigMap’te tutulması</li>
</ul>



<p class="wp-block-paragraph">Her teknik sorumluluk için tek bir birincil doğruluk kaynağı ve açık bir sahip belirlenmelidir.</p>



<h2 class="wp-block-heading">Sonuç: Spring Cloud mu Kubernetes mi?</h2>



<p class="wp-block-paragraph">Spring Cloud ve Kubernetes birbirlerinin tam alternatifi değildir.</p>



<p class="wp-block-paragraph">Spring Cloud uygulamaya discovery ve istemci davranışı kazandırır. Kubernetes temel platform bağlantısını ve servis yaşam döngüsünü yönetir. Istio ise ortak trafik, güvenlik ve gözlemlenebilirlik politikalarını uygulamadan ayırır.</p>



<p class="wp-block-paragraph">Kubernetes kullanan yeni sistemlerde service discovery ve temel load balancing platforma bırakılmalıdır. Istio yalnızca servis sayısı arttığı için değil, ortak trafik ve güvenlik politikalarının uygulama ekipleri tarafından ayrı ayrı yönetilmesi sürdürülemez hâle geldiğinde eklenmelidir.</p>



<p class="wp-block-paragraph">Kubernetes dışında veya ciddi hibrit altyapıda Consul güçlü bir seçimdir. Tamamen Java ve VM tabanlı küçük sistemlerde Eureka hâlâ yeterli olabilir. Kubernetes’e yerleşmiş bir platformda Eureka’yı kalıcı biçimde tutmak ise çoğunlukla aynı gerçeği iki kontrol düzleminde yönetmek anlamına gelir.</p>



<p class="wp-block-paragraph">Sağlıklı sorumluluk ayrımı şöyledir:</p>



<ul class="wp-block-list">
<li>Platform, servisin nerede olduğunu bilir.</li>



<li>Mesh, trafiğin teknik olarak nasıl akacağını bilir.</li>



<li>Uygulama, isteğin iş açısından ne anlama geldiğini bilir.</li>



<li>Konfigürasyon sistemi, uygulamanın hangi değerlerle çalışacağını bilir.</li>
</ul>



<p class="wp-block-paragraph">İyi mimari, bu sınırların açık olduğu ve aynı kararın birden fazla katmanda tekrar verilmediği mimaridir.</p>



<h2 class="wp-block-heading">Sık sorulan sorular</h2>



<h3 class="wp-block-heading">Kubernetes kullanırken Eureka gerekli mi?</h3>



<p class="wp-block-paragraph">Çoğu yeni Kubernetes sistemi için hayır. Kubernetes Service, DNS ve EndpointSlice temel hizmet keşfi ihtiyacını karşılar. Eureka ancak Kubernetes dışındaki servislerle geçiş, özel metadata tabanlı seçim veya güçlü bir mevcut bağımlılık varsa geçici olarak anlamlı olabilir.</p>



<h3 class="wp-block-heading">Kubernetes Service, Istio’nun yerini tutar mı?</h3>



<p class="wp-block-paragraph">Hayır. Kubernetes Service temel servis adresleme ve L4 yönlendirme sağlar. Istio ise canary, header tabanlı routing, mTLS, retry, circuit breaking ve ayrıntılı trafik gözlemlenebilirliği gibi gelişmiş politikalar ekler.</p>



<h3 class="wp-block-heading">Küçük bir mikroservis projesinde Istio kullanılmalı mı?</h3>



<p class="wp-block-paragraph">Genellikle hayır. Birkaç servis ve tek ekipten oluşan sistemlerde operasyonel maliyet çoğu zaman kazanımdan yüksektir. Önce Kubernetes’in yerleşik özellikleri ve doğru uygulama timeout’ları kullanılmalıdır.</p>



<h3 class="wp-block-heading">Consul mu Eureka mı daha uygun?</h3>



<p class="wp-block-paragraph">Tamamen Spring Boot kullanan sınırlı bir sistemde Eureka daha basit olabilir. Birden fazla dil, VM, container, veri merkezi veya hibrit altyapı söz konusuysa Consul daha geniş bir entegrasyon alanı sunar.</p>



<h3 class="wp-block-heading">Konfigürasyon değişikliği için dinamik refresh mi rolling restart mı?</h3>



<p class="wp-block-paragraph">Çoğu üretim konfigürasyonu için rolling restart daha öngörülebilir ve geri alınabilir bir modeldir. Feature flag gibi anlık değişmesi gereken değerlerde kontrollü dinamik konfigürasyon tercih edilebilir.</p>



<p class="wp-block-paragraph"></p>
<p><a href="https://alikarahisar.com/blog/spring-cloud-vs-kubernetes-service-discovery-trafik-yonetimi/">Spring Cloud vs. Kubernetes: Service Discovery ve Trafik Yönetimi Kime Emanet Edilmeli?</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alikarahisar.com/blog/spring-cloud-vs-kubernetes-service-discovery-trafik-yonetimi/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GraalVM Native Image ile Serverless ve Mikroservislerde Cold Start Sorununu Çözmek</title>
		<link>https://alikarahisar.com/blog/graalvm-native-image-cold-start-spring-boot-3/</link>
					<comments>https://alikarahisar.com/blog/graalvm-native-image-cold-start-spring-boot-3/#respond</comments>
		
		<dc:creator><![CDATA[Ali Karahisar]]></dc:creator>
		<pubDate>Thu, 03 Sep 2026 11:16:22 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Yazılım]]></category>
		<category><![CDATA[AOT derleme]]></category>
		<category><![CDATA[cloud-native Java]]></category>
		<category><![CDATA[GraalVM Native Image]]></category>
		<category><![CDATA[Java cold start]]></category>
		<category><![CDATA[JIT derleme]]></category>
		<category><![CDATA[memory footprint]]></category>
		<category><![CDATA[mikroservis performansı]]></category>
		<category><![CDATA[native executable]]></category>
		<category><![CDATA[serverless Java]]></category>
		<category><![CDATA[Spring AOT]]></category>
		<category><![CDATA[Spring Boot 3 native image]]></category>
		<guid isPermaLink="false">https://alikarahisar.com/blog/?p=44</guid>

					<description><![CDATA[<p>Java; güçlü ekosistemi, olgun araçları ve yüksek çalışma zamanı performansıyla mikroservis geliştirmede en güvenilir seçeneklerden biri. Ancak konu serverless fonksiyonlar, kısa ömürlü container’lar ve hızla ölçeklenen Kubernetes iş yükleri olduğunda klasik JVM modelinin iki maliyeti daha görünür hâle gelir: başlangıç süresi ve bellek tüketimi. Yeni bir instance’ın JVM’i başlatması, sınıfları yüklemesi, framework context’ini oluşturması ve [&#8230;]</p>
<p><a href="https://alikarahisar.com/blog/graalvm-native-image-cold-start-spring-boot-3/">GraalVM Native Image ile Serverless ve Mikroservislerde Cold Start Sorununu Çözmek</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Java; güçlü ekosistemi, olgun araçları ve yüksek çalışma zamanı performansıyla mikroservis geliştirmede en güvenilir seçeneklerden biri. Ancak konu serverless fonksiyonlar, kısa ömürlü container’lar ve hızla ölçeklenen Kubernetes iş yükleri olduğunda klasik JVM modelinin iki maliyeti daha görünür hâle gelir: başlangıç süresi ve bellek tüketimi.</p>



<span id="more-44"></span>



<p class="wp-block-paragraph">Yeni bir instance’ın JVM’i başlatması, sınıfları yüklemesi, framework context’ini oluşturması ve kodu optimize etmesi zaman alır. İstek trafiğinin olmadığı bir sürenin ardından fonksiyon yeniden oluşturuluyorsa kullanıcı bu maliyeti doğrudan “cold start” gecikmesi olarak hisseder.</p>



<p class="wp-block-paragraph">GraalVM Native Image, Java uygulamasını dağıtım öncesinde makine koduna derleyerek bu denklemi değiştirir. Uygulama, çalışma anında JAR ve tam bir JVM başlatmak yerine işletim sistemi üzerinde doğrudan çalışan platforma özgü bir executable olarak açılır. GraalVM belgelerine göre native executable yalnızca erişilebilir uygulama ve JDK kodunu, gerekli çalışma zamanı bileşenlerini ve statik olarak bağlanan yerel kodu içerir; bunun sonucu milisaniyeler düzeyinde başlangıç, warm-up gerektirmeyen performans ve daha düşük kaynak tüketimidir. (<a href="https://www.graalvm.org/latest/reference-manual/native-image/">GraalVM Native Image belgeleri</a>)</p>



<h2 class="wp-block-heading">Cold start neden oluşur?</h2>



<p class="wp-block-paragraph">Bir Java mikroservisinin ilk isteğe hazır hâle gelmeden önce tipik olarak şu işlemleri tamamlaması gerekir:</p>



<ol start="1" class="wp-block-list">
<li>JVM süreci başlatılır.</li>



<li>Uygulama sınıfları ve bağımlılıklar yüklenir.</li>



<li>Bytecode doğrulanır ve yorumlanmaya başlanır.</li>



<li>Spring ApplicationContext oluşturulur.</li>



<li>Bean tanımları, koşullu konfigürasyonlar ve proxy’ler hazırlanır.</li>



<li>Sık çalışan kod yolları belirlenir ve JIT compiler tarafından optimize edilir.</li>



<li>Bağlantı havuzları ve harici servis istemcileri hazırlanır.</li>
</ol>



<p class="wp-block-paragraph">Uzun süre çalışan bir serviste bu maliyet yalnızca bir kez ödenir ve JIT optimizasyonları zaman içinde önemli throughput avantajı sağlar. Buna karşılık serverless platformlarda, scale-to-zero kullanan Kubernetes ortamlarında veya çok sık pod oluşturan sistemlerde başlangıç maliyeti tekrar tekrar ödenebilir.</p>



<p class="wp-block-paragraph">Cold start yalnızca uygulamanın <code>main</code> metoduna ulaşma süresi değildir. Gerçek ölçüm, yeni instance’ın ilk isteğe doğru yanıt verebildiği ana kadar geçen süreyi kapsamalıdır.</p>



<h2 class="wp-block-heading">JIT ve AOT arasındaki mimari fark</h2>



<h3 class="wp-block-heading">JIT: Çalışırken öğrenen optimizasyon</h3>



<p class="wp-block-paragraph">Geleneksel JVM modelinde Java kaynak kodu önce bytecode’a çevrilir:</p>



<pre class="wp-block-code"><code>Java kaynak kodu
        ↓
     Bytecode
        ↓
 JVM + Interpreter
        ↓
   JIT Compiler
        ↓
Optimize makine kodu</code></pre>



<p class="wp-block-paragraph">JIT, uygulamanın gerçek çalışma davranışını gözlemler. Hangi metotların sık çağrıldığını, dallanmaların hangi yönde ilerlediğini ve hangi kod yollarının “hot” olduğunu belirleyerek bu bölümleri çalışma sırasında optimize eder.</p>



<p class="wp-block-paragraph">Bunun önemli bir avantajı vardır: Optimizasyon kararları gerçek trafik profilinden beslenir. Uzun süre çalışan ve CPU yoğun işlem yapan servislerde JVM, yeterli warm-up sonrasında çok yüksek tepe performansına ulaşabilir.</p>



<p class="wp-block-paragraph">Bedeli ise JVM’in, derleyicinin, profil verilerinin ve sınıf metadata’sının bellekte bulunmasıdır. Uygulama ayrıca en iyi performansına hemen değil, belirli bir ısınma süresinden sonra ulaşır.</p>



<h3 class="wp-block-heading">AOT: Çalışmadan önce tamamlanan derleme</h3>



<p class="wp-block-paragraph">GraalVM Native Image, bytecode’u build aşamasında analiz eder ve platforma özgü makine koduna dönüştürür:</p>



<pre class="wp-block-code"><code>Uygulama + bağımlılıklar + JDK sınıfları
                    ↓
      Erişilebilirlik analizi
                    ↓
       AOT optimizasyon ve derleme
                    ↓
 Kod + image heap + native runtime
                    ↓
        Native executable</code></pre>



<p class="wp-block-paragraph">Analiz <code>main</code> metodu gibi giriş noktalarından başlar. Erişilebilen sınıflar, metotlar ve alanlar tekrarlı biçimde bulunur; kullanılmadığı belirlenen parçalar native executable’a eklenmez. Derlemeden sonra yeni program elemanlarının çalışma anında sisteme eklenememesi “closed-world assumption”, yani kapalı dünya varsayımı olarak adlandırılır. (<a href="https://www.graalvm.org/latest/reference-manual/native-image/basics/">Native Image çalışma modeli</a>)</p>



<p class="wp-block-paragraph">Bazı sınıflar build sırasında initialize edilebilir. Oluşan nesneler ve statik durum “image heap” içerisine kaydedilir. Uygulama açıldığında bu işlerin yeniden yapılmaması startup süresini kısaltır.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><th>Özellik</th><th>JVM + JIT</th><th>GraalVM Native Image + AOT</th></tr><tr><td>Derleme zamanı</td><td>Çalışma sırasında devam eder</td><td>Büyük ölçüde build sırasında tamamlanır</td></tr><tr><td>Başlangıç</td><td>Class loading ve warm-up nedeniyle daha yavaş</td><td>Genellikle çok hızlı</td></tr><tr><td>Bellek</td><td>JVM, JIT ve metadata maliyeti vardır</td><td>Genellikle daha düşük RSS</td></tr><tr><td>Tepe throughput</td><td>Uzun ömürlü işlerde çok güçlü</td><td>İş yüküne göre JIT’in gerisinde kalabilir</td></tr><tr><td>Dinamik sınıf yükleme</td><td>Doğal olarak desteklenir</td><td>Kapalı dünya varsayımıyla sınırlıdır</td></tr><tr><td>Build süresi</td><td>Görece kısa</td><td>Daha uzun ve daha fazla kaynak tüketebilir</td></tr><tr><td>Dağıtım</td><td>JAR ve uyumlu JVM gerekir</td><td>İşletim sistemi ve mimariye özel binary gerekir</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">AOT her durumda JIT’ten daha hızlı demek değildir. Native Image’ın temel avantajı başlangıç ve kaynak verimliliğidir. Uzun süre çalışan, CPU yoğun servislerde JIT’in çalışma zamanı profiline dayanan optimizasyonları daha yüksek throughput sağlayabilir. GraalVM’in Profile-Guided Optimization özelliği, temsilî trafikten profil toplayıp bunu AOT derlemeye taşıyarak aradaki farkı azaltabilir. (<a href="https://www.graalvm.org/dev/reference-manual/native-image/optimizations-and-performance/">GraalVM optimizasyon belgeleri</a>)</p>



<h2 class="wp-block-heading">Startup süresi ve memory footprint üzerindeki etkisi</h2>



<p class="wp-block-paragraph">Native executable çalıştırılırken tam bir HotSpot JVM’in başlatılmasına ve uygulama kodunun JIT tarafından ısıtılmasına gerek kalmaz. Gereksiz sınıfların binary’ye alınmaması da class metadata, JIT code cache ve derleyici altyapısının çalışma zamanı maliyetlerini azaltır.</p>



<p class="wp-block-paragraph">Cloud-native sistemlerde bu özellikler üç alanda değer üretir:</p>



<ul class="wp-block-list">
<li>Serverless fonksiyonlar ilk isteğe daha kısa sürede cevap verebilir.</li>



<li>Kubernetes pod’ları daha hızlı hazır duruma gelerek scale-out gecikmesini azaltabilir.</li>



<li>Aynı bellek bütçesi içerisinde daha fazla servis instance’ı çalıştırılabilir.</li>
</ul>



<p class="wp-block-paragraph">Ancak sonucu tek bir evrensel yüzdeyle ifade etmek doğru değildir. Kazanç; kullanılan framework’e, dependency sayısına, heap ayarlarına, GC seçimine, container limitlerine ve uygulamanın başlangıç sırasında yaptığı I/O işlemlerine bağlıdır.</p>



<p class="wp-block-paragraph">Örneğin uygulama açılışta veritabanı migrasyonu çalıştırıyor veya uzak bir secret servisine bağlanıyorsa native executable bu ağ gecikmesini ortadan kaldırmaz. GraalVM kod başlatma maliyetini azaltır; dış sistemlerin gecikmesini sihirli biçimde çözmez.</p>



<p class="wp-block-paragraph">Bu nedenle JVM ve native sürümleri aynı koşullarda karşılaştırın:</p>



<pre class="wp-block-code"><code># JVM sürümü
/usr/bin/time -v java -jar target/cold-start-demo.jar

# Native sürüm
/usr/bin/time -v ./target/cold-start-demo</code></pre>



<p class="wp-block-paragraph">macOS üzerinde ayrıntılı süreç ölçümü için:</p>



<pre class="wp-block-code"><code>/usr/bin/time -l ./target/cold-start-demo</code></pre>



<p class="wp-block-paragraph">Yalnızca konsoldaki “Started” mesajını değil, aşağıdaki metrikleri kaydedin:</p>



<ul class="wp-block-list">
<li>Process başlangıcından readiness durumuna kadar geçen süre</li>



<li>İlk isteğin p50, p95 ve p99 gecikmesi</li>



<li>Warm request gecikmesi</li>



<li>Peak RSS ve steady-state RSS</li>



<li>CPU tüketimi</li>



<li>Binary veya container image boyutu</li>



<li>Native image build süresi</li>



<li>Gerçek trafik altındaki throughput</li>
</ul>



<h2 class="wp-block-heading">GraalVM Native Image’ın zorlandığı alanlar</h2>



<h3 class="wp-block-heading">1. Reflection</h3>



<p class="wp-block-paragraph">Reflection kullanılan kodda çağrılacak sınıf veya metot adı çalışma anında bir konfigürasyondan gelebilir. Statik analiz bu ilişkiyi göremiyorsa ilgili sınıfın kullanılmadığını düşünerek onu binary dışında bırakabilir.</p>



<p class="wp-block-paragraph">Sonuç olarak uygulama başarıyla derlense bile yalnızca belirli bir endpoint çağrıldığında hata verebilir. JSON serileştirme, ORM, dependency injection ve plugin tabanlı kütüphaneler bu açıdan özellikle test edilmelidir.</p>



<p class="wp-block-paragraph">Spring Boot 3’ün AOT motoru controller dönüş tipleri, bean tanımları ve pek çok framework kullanımına ait hint’leri otomatik üretir. Otomatik çıkarımın yetmediği durumlarda <code>@RegisterReflectionForBinding</code> veya <code>RuntimeHintsRegistrar</code> kullanılabilir. (<a href="https://docs.spring.io/spring-boot/reference/packaging/native-image/advanced-topics.html">Spring Boot özel native hint rehberi</a>)</p>



<p class="wp-block-paragraph">Örneğin çalışma anında JSON’a dönüştürülecek bir sınıfı açıkça kaydetmek için:</p>



<pre class="wp-block-code"><code>import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.context.annotation.Configuration;

@Configuration
@RegisterReflectionForBinding(PaymentRequest.class)
class NativeConfiguration {
}</code></pre>



<h3 class="wp-block-heading">2. Dynamic proxy</h3>



<p class="wp-block-paragraph">Spring AOP, declarative transaction yönetimi ve bazı HTTP client kütüphaneleri çalışma anında proxy oluşturabilir. JVM üzerinde ihtiyaç anında üretilebilen bu sınıfların native executable içinde önceden bilinmesi gerekir.</p>



<p class="wp-block-paragraph">Spring AOT, kendi tanıyabildiği proxy’leri build sırasında üretir. Uygulamaya özgü bir JDK proxy için runtime hint eklenebilir:</p>



<pre class="wp-block-code"><code>import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;

class ApplicationRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(
            RuntimeHints hints,
            ClassLoader classLoader) {

        hints.proxies().registerJdkProxy(PaymentClient.class);
    }
}</code></pre>



<p class="wp-block-paragraph">Registrar’ı uygulamaya bağlayalım:</p>



<pre class="wp-block-code"><code>import org.springframework.context.annotation.ImportRuntimeHints;

@SpringBootApplication
@ImportRuntimeHints(ApplicationRuntimeHints.class)
public class PaymentApplication {

    public static void main(String&#91;] args) {
        SpringApplication.run(PaymentApplication.class, args);
    }
}</code></pre>



<p class="wp-block-paragraph">Spring’in CGLIB tabanlı proxy’leri de JVM’de çalışma anında oluşturulurken native build sürecinde önceden üretilir. Oluşturulan proxy sınıfları Maven projelerinde <code>target/spring-aot/main/classes</code> altında incelenebilir. (<a href="https://docs.spring.io/spring-boot/reference/packaging/native-image/introducing-graalvm-native-images.html">Spring AOT proxy üretimi</a>)</p>



<h3 class="wp-block-heading">3. Classpath kaynakları</h3>



<p class="wp-block-paragraph">JSON şablonları, sertifikalar, SQL dosyaları, localization bundle’ları veya statik dosyalar Java kodu olmadığı için yalnızca erişilebilirlik analiziyle her zaman bulunamaz.</p>



<p class="wp-block-paragraph">Bir kaynağı Spring runtime hints API ile eklemek mümkündür:</p>



<pre class="wp-block-code"><code>@Override
public void registerHints(
        RuntimeHints hints,
        ClassLoader classLoader) {

    hints.resources().registerPattern("templates/*.json");
    hints.resources().registerPattern("db/migration/**");
}</code></pre>



<p class="wp-block-paragraph">GraalVM ayrıca <code>META-INF/native-image</code> altındaki <code>reachability-metadata.json</code> dosyalarını okuyabilir:</p>



<pre class="wp-block-code"><code>{
  "resources": &#91;
    {
      "glob": "templates/*.json"
    }
  ]
}</code></pre>



<p class="wp-block-paragraph">Native executable içine gömülen kaynaklar gerçek bir dosya sistemi yolu olmayabilir. Bu nedenle <code>URL#getFile()</code> yerine <code>getResourceAsStream()</code> veya <code>URL#openStream()</code> tercih edilmelidir. Gerekli metadata bulunmayan dinamik erişimler çalışma zamanı hatasına dönüşebilir. (<a href="https://www.graalvm.org/jdk25/reference-manual/native-image/metadata/">GraalVM Reachability Metadata</a>)</p>



<h2 class="wp-block-heading">Spring Boot 3 ile native executable oluşturma</h2>



<p class="wp-block-paragraph">Aşağıdaki örnek Maven tabanlı bir Spring Boot 3 projesini temel alır.</p>



<h3 class="wp-block-heading">Adım 1: Projeyi oluşturun</h3>



<p class="wp-block-paragraph"><a href="https://start.spring.io/">start.spring.io</a> üzerinden şu seçimlerle bir proje oluşturabilirsiniz:</p>



<ul class="wp-block-list">
<li>Java 21 gibi desteklenen bir LTS sürümü</li>



<li>Spring Boot 3.x</li>



<li>Maven</li>



<li>Spring Web</li>



<li>GraalVM Native Support</li>
</ul>



<p class="wp-block-paragraph">Basit bir endpoint ekleyelim:</p>



<pre class="wp-block-code"><code>package com.example.coldstart;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class ColdStartApplication {

    @GetMapping("/hello")
    String hello() {
        return "Hello from GraalVM Native Image";
    }

    public static void main(String&#91;] args) {
        SpringApplication.run(ColdStartApplication.class, args);
    }
}</code></pre>



<h3 class="wp-block-heading">Adım 2: Maven plugin’lerini kontrol edin</h3>



<p class="wp-block-paragraph">Spring Boot parent’ın sağladığı <code>native</code> profilinden yararlanmak için build bölümünde şu plugin’ler bulunmalıdır:</p>



<pre class="wp-block-code"><code>&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
        &lt;/plugin&gt;

        &lt;plugin&gt;
            &lt;groupId&gt;org.graalvm.buildtools&lt;/groupId&gt;
            &lt;artifactId&gt;native-maven-plugin&lt;/artifactId&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;</code></pre>



<p class="wp-block-paragraph">Spring AOT işlemi ApplicationContext’i build aşamasında analiz eder; bean tanımlarına ve kapalı dünya varsayımına uygun optimize edilmiş kod ile GraalVM hint dosyaları üretir. Maven çıktıları <code>target/spring-aot</code> altında incelenebilir. (<a href="https://docs.spring.io/spring-boot/3.3/maven-plugin/aot.html">Spring Boot AOT belgeleri</a>)</p>



<h3 class="wp-block-heading">Adım 3: Yerel native executable üretin</h3>



<p class="wp-block-paragraph">Makinede kullandığınız Java sürümüyle uyumlu bir GraalVM dağıtımı ve gerekli native toolchain kurulu olmalıdır. Ardından:</p>



<pre class="wp-block-code"><code>./mvnw -Pnative native:compile</code></pre>



<p class="wp-block-paragraph">Executable varsayılan olarak <code>target</code> dizininde oluşur:</p>



<pre class="wp-block-code"><code>./target/cold-start-demo</code></pre>



<p class="wp-block-paragraph">Servisi doğrulayın:</p>



<pre class="wp-block-code"><code>curl http://localhost:8080/hello</code></pre>



<p class="wp-block-paragraph">Gradle kullanan projelerde GraalVM Native Build Tools plugin’i uygulandıktan sonra eşdeğer komutlar şunlardır:</p>



<pre class="wp-block-code"><code>./gradlew nativeCompile
./build/native/nativeCompile/cold-start-demo</code></pre>



<h3 class="wp-block-heading">Adım 4: Buildpack ile native container image oluşturun</h3>



<p class="wp-block-paragraph">Yerel GraalVM kurulumu yerine Docker ve Cloud Native Buildpacks kullanabilirsiniz:</p>



<pre class="wp-block-code"><code>./mvnw -Pnative spring-boot:build-image</code></pre>



<p class="wp-block-paragraph">Oluşan image’ı çalıştırın:</p>



<pre class="wp-block-code"><code>docker run --rm -p 8080:8080 \
  docker.io/library/cold-start-demo:0.0.1-SNAPSHOT</code></pre>



<p class="wp-block-paragraph">Gradle karşılığı:</p>



<pre class="wp-block-code"><code>./gradlew bootBuildImage</code></pre>



<p class="wp-block-paragraph">Spring Boot, Maven ve Gradle üzerinden Paketo buildpack ile JVM içermeyen native container image üretilmesini destekler. (<a href="https://docs.spring.io/spring-boot/3.4/how-to/native-image/developing-your-first-application.html">Spring Boot native uygulama rehberi</a>)</p>



<p class="wp-block-paragraph">Native Image doğrudan cross-compilation yapmadığından executable hedef işletim sistemi ve CPU mimarisi için üretilmelidir. Örneğin Linux AMD64 ile Linux ARM64 artefact’larını ayrı runner’larda veya uygun multi-platform build altyapısında oluşturmak gerekir.</p>



<h3 class="wp-block-heading">Adım 5: Native test çalıştırın</h3>



<p class="wp-block-paragraph">Normal JVM testleri hızlı geri bildirim sağlar; ancak kapalı dünya varsayımından kaynaklanan sorunları yakalamak için native test de CI sürecine eklenmelidir:</p>



<pre class="wp-block-code"><code>./mvnw -Pnative native:test</code></pre>



<p class="wp-block-paragraph">Gradle için:</p>



<pre class="wp-block-code"><code>./gradlew nativeTest</code></pre>



<p class="wp-block-paragraph">Özellikle şu yolları test edin:</p>



<ul class="wp-block-list">
<li>JSON serialization ve deserialization</li>



<li>Reflection kullanan kod</li>



<li>AOP ve transaction proxy’leri</li>



<li>Dosya, template ve resource bundle erişimi</li>



<li>SSL ve güvenlik sağlayıcıları</li>



<li>ORM entity’leri</li>



<li>Dinamik olarak seçilen implementasyonlar</li>
</ul>



<h3 class="wp-block-heading">Adım 6: Eksik metadata’yı tracing agent ile bulun</h3>



<p class="wp-block-paragraph">Spring’in otomatik hint üretiminin yetmediği eski veya uyumsuz bir kütüphanede uygulamayı önce JVM üzerinde tracing agent ile çalıştırabilirsiniz:</p>



<pre class="wp-block-code"><code>java -Dspring.aot.enabled=true \
  -agentlib:native-image-agent=config-output-dir=target/native-agent \
  -jar target/cold-start-demo.jar</code></pre>



<p class="wp-block-paragraph">Uygulamanın kritik akışlarını çalıştırdıktan sonra oluşan metadata’yı inceleyin. Gerekli dosyalar kontrollü biçimde <code>src/main/resources/META-INF/native-image/</code> altına taşınabilir.</p>



<p class="wp-block-paragraph">Tracing agent yalnızca çalıştırılan yolları görür. Eksiksiz test kapsamı yoksa gerekli metadata’nın bir kısmı yine atlanabilir; üretilen dosyaları körlemesine kabul etmek yerine sadeleştirmek ve doğrulamak gerekir. GraalVM’in agent’ı reflection, proxy, JNI ve resource erişimlerini kaydedebilir. (<a href="https://www.graalvm.org/jdk25/reference-manual/native-image/metadata/AutomaticMetadataCollection/">GraalVM tracing agent rehberi</a>)</p>



<h2 class="wp-block-heading">Üretim ortamı için mimari öneriler</h2>



<p class="wp-block-paragraph">GraalVM Native Image özellikle şu iş yüklerinde güçlü bir seçenektir:</p>



<ul class="wp-block-list">
<li>Trafiği düzensiz serverless fonksiyonlar</li>



<li>Scale-to-zero kullanan servisler</li>



<li>Çok hızlı yatay ölçeklenmesi gereken API’ler</li>



<li>Düşük bellek limitli container’lar</li>



<li>CLI uygulamaları ve kısa ömürlü batch işler</li>



<li>Edge veya yoğun çoklu servis ortamları</li>
</ul>



<p class="wp-block-paragraph">Buna karşılık aşağıdaki durumlarda geleneksel JVM seçeneğini ayrıca benchmark etmek gerekir:</p>



<ul class="wp-block-list">
<li>Günlerce çalışan CPU yoğun servisler</li>



<li>Yoğun dinamik class loading veya plugin mimarisi</li>



<li>Native desteği zayıf üçüncü taraf kütüphaneler</li>



<li>Çok sık değişen runtime bean yapıları</li>



<li>Derleme süresi ve CI kaynak maliyetinin kritik olduğu projeler</li>
</ul>



<p class="wp-block-paragraph">İyi bir cloud-native strateji bütün servisleri zorunlu olarak native’e çevirmek değildir. Cold start ve bellek baskısı yaşayan sınır servislerini native executable olarak çalıştırırken, uzun ömürlü ve throughput odaklı servisleri JVM üzerinde bırakmak daha dengeli olabilir.</p>



<h2 class="wp-block-heading">Sonuç</h2>



<p class="wp-block-paragraph">GraalVM Native Image, Java’nın serverless ve mikroservis mimarilerindeki en görünür dezavantajlarından ikisini doğrudan hedefler: cold start gecikmesi ve yüksek memory footprint.</p>



<p class="wp-block-paragraph">AOT derleme; erişilebilir kodu, gerekli runtime bileşenlerini ve belirli başlangıç durumlarını build aşamasında native executable içine taşır. Böylece uygulama hızlı açılır, warm-up ihtiyacı azalır ve daha küçük bir çalışma zamanı belleğiyle hizmet verebilir.</p>



<p class="wp-block-paragraph">Bu kazanımın karşılığında closed-world assumption nedeniyle reflection, dynamic proxy, serialization ve classpath kaynaklarının daha bilinçli yönetilmesi gerekir. Spring Boot 3’ün AOT altyapısı bu işin büyük bölümünü otomatikleştirir; <code>RuntimeHints</code>, reachability metadata, tracing agent ve native testler ise kalan boşlukları kapatır.</p>



<p class="wp-block-paragraph">Doğru karar için son adım her zaman aynıdır: JVM ve native sürümü gerçek trafik profiliniz, hedef container limitleriniz ve ölçeklenme modeliniz altında ölçün. Cold start kritikse GraalVM Native Image çoğu zaman yalnızca bir optimizasyon değil, doğrudan mimari bir avantajdır.</p>



<h2 class="wp-block-heading">Sık Sorulan Sorular</h2>



<h3 class="wp-block-heading">GraalVM Native Image tamamen JVM’siz mi çalışır?</h3>



<p class="wp-block-paragraph">Üretilen executable’ın yanında ayrı bir HotSpot JVM bulunmaz. Ancak binary; garbage collector, thread desteği ve gerekli çalışma zamanı servisleri gibi Native Image runtime bileşenlerini içerir.</p>



<h3 class="wp-block-heading">Native Image her Java uygulamasında daha hızlı mıdır?</h3>



<p class="wp-block-paragraph">Hayır. Başlangıç süresi genellikle çok daha iyidir; fakat uzun süre çalışan ve CPU yoğun uygulamalarda JIT daha yüksek tepe throughput sağlayabilir. Karar benchmark sonucuna göre verilmelidir.</p>



<h3 class="wp-block-heading">Spring Boot 3 reflection sorunlarını otomatik çözer mi?</h3>



<p class="wp-block-paragraph">Spring AOT birçok yaygın kullanım için gerekli hint’leri otomatik üretir. Uygulamaya özel reflection, dinamik proxy veya üçüncü taraf kütüphane kullanımlarında ek runtime hint gerekebilir.</p>



<h3 class="wp-block-heading">Native executable bir kez üretilip her platformda çalıştırılabilir mi?</h3>



<p class="wp-block-paragraph">Hayır. Binary hedef işletim sistemi ve CPU mimarisine özeldir. Linux, macOS, AMD64 ve ARM64 hedefleri için uygun ortamlarda ayrı build alınmalıdır.</p>



<h3 class="wp-block-heading">GraalVM Native Image serverless maliyetini azaltır mı?</h3>



<p class="wp-block-paragraph">Daha hızlı startup ve düşük bellek tüketimi, platformun fiyatlandırma modeline bağlı olarak süre ve kaynak maliyetini azaltabilir. Kesin kazanç ancak gerçek provider, trafik ve bellek ayarlarıyla yapılan ölçüm sonucunda belirlenebilir.</p>



<p class="wp-block-paragraph"></p>
<p><a href="https://alikarahisar.com/blog/graalvm-native-image-cold-start-spring-boot-3/">GraalVM Native Image ile Serverless ve Mikroservislerde Cold Start Sorununu Çözmek</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alikarahisar.com/blog/graalvm-native-image-cold-start-spring-boot-3/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Garbage Collector Seçimi ve Akort Edilmesi: G1 GC vs. ZGC</title>
		<link>https://alikarahisar.com/blog/garbage-collector-secimi-g1-gc-vs-zgc/</link>
					<comments>https://alikarahisar.com/blog/garbage-collector-secimi-g1-gc-vs-zgc/#respond</comments>
		
		<dc:creator><![CDATA[Ali Karahisar]]></dc:creator>
		<pubDate>Wed, 02 Sep 2026 19:05:32 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Yazılım]]></category>
		<category><![CDATA[G1 GC]]></category>
		<category><![CDATA[Garbage Collector seçimi]]></category>
		<category><![CDATA[GC pause time]]></category>
		<category><![CDATA[Generational ZGC]]></category>
		<category><![CDATA[Java performans optimizasyonu]]></category>
		<category><![CDATA[JVM flags]]></category>
		<category><![CDATA[JVM tuning]]></category>
		<category><![CDATA[large heap]]></category>
		<category><![CDATA[low latency Java]]></category>
		<category><![CDATA[ZGC]]></category>
		<guid isPermaLink="false">https://alikarahisar.com/blog/?p=39</guid>

					<description><![CDATA[<p>Java uygulamalarında Garbage Collector seçimi yalnızca bellek yönetimiyle ilgili teknik bir ayrıntı değildir. GC; API yanıt sürelerini, işlem hacmini, altyapı maliyetini ve sistemin yoğun trafik altındaki kararlılığını doğrudan etkiler. Özellikle onlarca veya yüzlerce gigabaytlık heap kullanan sistemlerde ortalama GC süresine bakmak yeterli değildir. Kullanıcı deneyimini ve servis seviyesi hedeflerini çoğunlukla p99, p99.9 ve p99.99 gecikmeleri [&#8230;]</p>
<p><a href="https://alikarahisar.com/blog/garbage-collector-secimi-g1-gc-vs-zgc/">Garbage Collector Seçimi ve Akort Edilmesi: G1 GC vs. ZGC</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Java uygulamalarında Garbage Collector seçimi yalnızca bellek yönetimiyle ilgili teknik bir ayrıntı değildir. GC; API yanıt sürelerini, işlem hacmini, altyapı maliyetini ve sistemin yoğun trafik altındaki kararlılığını doğrudan etkiler.</p>



<span id="more-39"></span>



<p class="wp-block-paragraph">Özellikle onlarca veya yüzlerce gigabaytlık heap kullanan sistemlerde ortalama GC süresine bakmak yeterli değildir. Kullanıcı deneyimini ve servis seviyesi hedeflerini çoğunlukla p99, p99.9 ve p99.99 gecikmeleri belirler. Bu nedenle doğru soru “Hangi GC daha hızlı?” değil, “Bu iş yükünün gecikme, throughput ve kaynak tüketimi hedefleri için hangi GC daha uygun?” olmalıdır.</p>



<p class="wp-block-paragraph">G1 GC, dengeli throughput ve öngörülebilir duraklamalar sağlamayı amaçlayan genel kullanıma yönelik bir collector’dır. ZGC ise daha fazla işi uygulamayla eş zamanlı yürüterek çok büyük heap’lerde dahi ultra düşük duraklama sürelerini hedefler.</p>



<p class="wp-block-paragraph">Oracle’ın güncel sınıflandırmasına göre G1 “mostly concurrent”, ZGC ise “fully concurrent” bir collector’dır. Oracle, yanıt süresinin en yüksek öncelik olduğu sistemlerde ZGC’yi başlangıç seçeneği olarak önermektedir. Bununla birlikte ZGC’nin düşük duraklama avantajı, iş yüküne bağlı CPU ve bellek maliyetleriyle birlikte değerlendirilmelidir. <a href="https://docs.oracle.com/en/java/javase/25/gctuning/available-collectors.html" target="_blank" rel="noreferrer noopener">Oracle Java 25 GC seçim rehberi</a></p>



<h2 class="wp-block-heading">Kısa cevap: G1 GC mi, ZGC mi?</h2>



<p class="wp-block-paragraph">Aşağıdaki yaklaşım pratik bir başlangıç noktasıdır:</p>



<ul class="wp-block-list">
<li>Dengeli throughput, makul bellek tüketimi ve yüzlerce milisaniyenin altında tutulmak istenen GC duraklamaları için G1 GC güçlü bir varsayılan seçimdir.</li>



<li>Onlarca ya da yüzlerce gigabaytlık heap, yüksek canlı veri miktarı ve tek haneli milisaniyelik uçtan uca gecikme hedefleri varsa ZGC öncelikli adaydır.</li>



<li>CPU kapasitesi sınırlıysa veya en önemli hedef maksimum işlem hacmiyse, ZGC’nin concurrent çalışma maliyeti mutlaka ölçülmelidir.</li>



<li>GC değişikliği sentetik mikro benchmark sonucuyla değil, üretime benzeyen yük, veri dağılımı ve allocation profiliyle doğrulanmalıdır.</li>
</ul>



<p class="wp-block-paragraph">ZGC düşük gecikme için güçlü bir mimari avantaj sağlar; fakat gerçek zamanlı bir collector değildir ve uygulama için kesin bir SLA garantisi vermez. İşletim sistemi zamanlaması, safepoint işlemleri, JIT derlemesi, disk erişimi, sayfa hataları, kilit rekabeti ve dış servisler de tail latency üretebilir.</p>



<h2 class="wp-block-heading">G1 GC nasıl çalışır?</h2>



<p class="wp-block-paragraph">Garbage-First Garbage Collector, heap’i eşit büyüklükte bölgelere ayırır. Bu bölgeler sabit biçimde Eden, Survivor veya Old olarak ayrılmış değildir; ihtiyaç oldukça farklı roller üstlenebilir.</p>



<p class="wp-block-paragraph">G1; generational, bölge tabanlı, evacuating ve çoğunlukla eş zamanlı çalışan bir collector’dır. Global canlılık analizi gibi pahalı işlerin bir bölümünü uygulama thread’leri çalışırken gerçekleştirir. Buna karşılık canlı nesnelerin seçilmiş bölgelerden başka bölgelere kopyalanması ağırlıklı olarak Stop-The-World duraklamaları içinde yapılır.</p>



<p class="wp-block-paragraph">Oracle’ın tanımına göre G1, belirlenen pause-time hedefine yüksek olasılıkla ulaşmaya çalışır; ancak bu hedef her GC duraklaması için kesin bir üst sınır değildir. <a href="https://docs.oracle.com/en/java/javase/25/gctuning/garbage-first-g1-garbage-collector1.html" target="_blank" rel="noreferrer noopener">Oracle G1 GC mimarisi</a></p>



<h3 class="wp-block-heading">G1 GC döngüsü</h3>



<p class="wp-block-paragraph">G1’in çalışma modeli iki ana dönem halinde düşünülebilir.</p>



<h4 class="wp-block-heading">1. Young-only dönemi</h4>



<p class="wp-block-paragraph">Yeni nesilde biriken nesneler young collection ile toplanır. Yaşamaya devam eden nesneler Survivor bölgelerine veya yaşlarına göre Old Generation’a taşınır.</p>



<p class="wp-block-paragraph">Young collection sırasında uygulama thread’leri durdurulur. G1, bir sonraki young generation boyutunu geçmiş duraklama sürelerinden ve kopyalanan canlı nesne miktarından yararlanarak hesaplar.</p>



<h4 class="wp-block-heading">2. Concurrent marking ve space-reclamation dönemi</h4>



<p class="wp-block-paragraph">Old Generation doluluğu Initiating Heap Occupancy eşiğine ulaştığında G1 concurrent marking döngüsünü başlatır:</p>



<ol start="1" class="wp-block-list">
<li>Bir Concurrent Start young collection yapılır.</li>



<li>Old Generation içindeki erişilebilir nesneler uygulamayla eş zamanlı işaretlenir.</li>



<li>Remark duraklamasında işaretleme sonuçlandırılır.</li>



<li>Cleanup aşamasında bölgelerin canlılık oranları hesaplanır.</li>



<li>Mixed collection’larda young bölgelerle birlikte seçilmiş old bölgeler de boşaltılır.</li>
</ol>



<p class="wp-block-paragraph">“Garbage-First” adı, collector’ın önce en fazla çöp içeren ve en verimli biçimde geri kazanılabilecek bölgeleri seçmesinden gelir.</p>



<h3 class="wp-block-heading">G1’in pause-time modeli</h3>



<p class="wp-block-paragraph"><code>-XX:MaxGCPauseMillis</code> bir zaman sınırı değil, ergonomik bir hedeftir. G1, collection set’e eklenecek bölgelerin sayısını ve young generation büyüklüğünü bu hedefe göre ayarlamaya çalışır.</p>



<p class="wp-block-paragraph">Örneğin 100 milisaniyelik hedef vermek, hiçbir GC duraklamasının 100 milisaniyeyi geçmeyeceği anlamına gelmez. Çok yüksek canlı nesne oranı, ani allocation artışı, reference processing, remembered set taraması veya humongous object yoğunluğu hedefin aşılmasına neden olabilir.</p>



<h3 class="wp-block-heading">Remembered set ve card table maliyeti</h3>



<p class="wp-block-paragraph">G1, bölgeler arasındaki referansları bulabilmek için card table ve remembered set yapılarından yararlanır. Uygulama bir referans alanını değiştirdiğinde write barrier ilgili kartı işaretler. Böylece young collection sırasında bütün Old Generation’ın taranması gerekmez.</p>



<p class="wp-block-paragraph">Bu mekanizma toplama işini sınırlar; ancak uygulama thread’lerine barrier maliyeti, ek metadata tüketimi ve remembered set bakım yükü getirir. Nesne grafiğinde çok yoğun bölge geçişleri bulunan sistemlerde bu maliyet görünür hale gelebilir.</p>



<h3 class="wp-block-heading">Evacuation ve Full GC riski</h3>



<p class="wp-block-paragraph">G1, canlı nesneleri seçilmiş bölgelerden boş alanlara taşıyarak belleği sıkıştırır. Taşıma için yeterli boş bölge kalmazsa evacuation failure oluşabilir.</p>



<p class="wp-block-paragraph">Concurrent marking allocation hızına yetişemez ve yeterli alan geri kazanılamazsa G1, son çare olarak Stop-The-World Full GC gerçekleştirebilir. Büyük heap’lerde bu durum saniyelere ulaşabilen ciddi latency sıçramaları oluşturabilir.</p>



<h3 class="wp-block-heading">Humongous object davranışı</h3>



<p class="wp-block-paragraph">Boyutu bir G1 bölgesinin yarısından büyük olan nesneler “humongous” olarak değerlendirilir ve ardışık bölgelerde tutulur. Büyük byte dizileri, sıkıştırılmış mesajlar, cache blokları ve büyük JSON payload’ları bu kategoriye girebilir.</p>



<p class="wp-block-paragraph">Humongous allocation:</p>



<ul class="wp-block-list">
<li>Heap parçalanmasını artırabilir.</li>



<li>Concurrent marking döngüsünü erken başlatabilir.</li>



<li>Ardışık boş bölge ihtiyacı doğurabilir.</li>



<li>Full GC ve hatta <code>OutOfMemoryError</code> riskini yükseltebilir.</li>
</ul>



<p class="wp-block-paragraph">GC loglarında humongous region sayısı yüksekse ilk çözüm JVM bayrağı eklemek değil, büyük allocation kaynaklarını uygulama seviyesinde incelemek olmalıdır.</p>



<h2 class="wp-block-heading">ZGC nasıl çalışır?</h2>



<p class="wp-block-paragraph">ZGC, ölçeklenebilir ve düşük gecikme odaklı, concurrent bir collector’dır. Marking, reference processing ve nesne relocation gibi pahalı işlerin büyük bölümünü uygulama thread’leri çalışırken gerçekleştirir.</p>



<p class="wp-block-paragraph">Oracle’ın güncel rehberine göre ZGC’nin duraklama süreleri kullanılan heap boyutundan bağımsızdır ve collector birkaç yüz megabayttan 16 TB’a kadar heap’lerde çalışabilir. JDK 25 dokümantasyonu maksimum duraklamaları bir milisaniyenin altında hedefleyen çalışma modelini açıklamaktadır. <a href="https://docs.oracle.com/en/java/javase/24/gctuning/z-garbage-collector.html" target="_blank" rel="noreferrer noopener">Oracle ZGC rehberi</a></p>



<p class="wp-block-paragraph">Buradaki kritik ifade “heap boyutundan bağımsız” olmasıdır. Bu, heap büyüdükçe concurrent toplam iş miktarının hiç artmadığı anlamına gelmez. Artan canlı veri daha fazla CPU zamanı ve daha uzun concurrent cycle gerektirebilir. Mimari avantaj, pahalı işin Stop-The-World penceresine taşınmamasıdır.</p>



<h3 class="wp-block-heading">Generational ZGC ve JDK sürümü</h3>



<p class="wp-block-paragraph">ZGC’nin nesilleri kullanan sürümü JDK 21 ile kullanıma sunuldu. Generational ZGC, çoğu nesnenin genç yaşta öldüğü gözleminden yararlanarak young ve old nesilleri farklı sıklıklarda toplar.</p>



<p class="wp-block-paragraph">Sürüm davranışı önemlidir:</p>



<ul class="wp-block-list">
<li>JDK 21 ve 22’de generational mod <code>-XX:+ZGenerational</code> ile etkinleştirilir.</li>



<li>JDK 23’te generational mod varsayılan hale gelmiştir.</li>



<li>JDK 24 itibarıyla non-generational ZGC kaldırılmış ve <code>ZGenerational</code> seçeneğine ihtiyaç kalmamıştır.</li>
</ul>



<p class="wp-block-paragraph">Dolayısıyla internetten kopyalanan eski bir JVM başlangıç satırı yeni JDK’da geçersiz olabilir. Bayraklar hedef JDK dağıtımı ve sürümü üzerinde doğrulanmalıdır. <a href="https://docs.oracle.com/en/java/javase/25/migrate/significant-changes-jdk-21.html" target="_blank" rel="noreferrer noopener">Oracle JDK 21 değişiklikleri</a> ve <a href="https://docs.oracle.com/en/java/javase/25/migrate/significant-changes-jdk-24.html" target="_blank" rel="noreferrer noopener">Oracle JDK 24 değişiklikleri</a></p>



<h3 class="wp-block-heading">Colored pointers</h3>



<p class="wp-block-paragraph">ZGC’nin temel yapı taşlarından biri colored pointer yaklaşımıdır. Nesne referansları yalnızca bellek adresi taşımaz; marking ve relocation durumuyla ilgili metadata da içerir.</p>



<p class="wp-block-paragraph">Bu bilgi sayesinde JVM bir referansın:</p>



<ul class="wp-block-list">
<li>Geçerli GC görünümüne ait olup olmadığını,</li>



<li>İşaretleme işlemi gerektirip gerektirmediğini,</li>



<li>Nesnenin taşınmış olup olmadığını,</li>



<li>Referansın güncellenmesi gerekip gerekmediğini</li>
</ul>



<p class="wp-block-paragraph">kontrol edebilir.</p>



<p class="wp-block-paragraph">Colored pointer yaklaşımı, ZGC’nin nesneleri uygulama thread’leri çalışırken taşıyabilmesinin önemli nedenlerinden biridir.</p>



<h3 class="wp-block-heading">Load ve store barrier’lar</h3>



<p class="wp-block-paragraph">Uygulama bir nesne referansını okuduğunda veya güncellediğinde JIT tarafından üretilen barrier kodu devreye girebilir. Barrier, referansın GC açısından güvenli ve güncel olmasını sağlar.</p>



<p class="wp-block-paragraph">Bir nesne concurrent relocation sırasında başka bir konuma taşınmışsa barrier doğru konumu bulabilir ve referansı düzeltebilir. Bu nedenle bütün uygulama thread’lerini durdurup heap genelindeki tüm referansları tek seferde güncellemek gerekmez.</p>



<p class="wp-block-paragraph">Generational ZGC ayrıca young ve old nesiller arasındaki referansları verimli biçimde izlemek için optimize edilmiş barrier mekanizmaları kullanır. Bunun karşılığı, uygulamanın normal nesne erişim yollarında belirli bir CPU maliyetidir.</p>



<h3 class="wp-block-heading">Concurrent marking</h3>



<p class="wp-block-paragraph">ZGC, erişilebilir nesneleri uygulama çalışırken işaretler. Kısa Stop-The-World evreleri temel olarak kök durumunu güvenli biçimde yakalamak ve bazı faz geçişlerini koordine etmek için kullanılır.</p>



<p class="wp-block-paragraph">Duraklamalarda bütün canlı nesne kümesini dolaşmak yerine sınırlı ve heap boyutuna doğrudan bağlı olmayan işler yapılması, large heap sistemlerde ZGC’nin en önemli avantajıdır.</p>



<h3 class="wp-block-heading">Concurrent relocation</h3>



<p class="wp-block-paragraph">ZGC, seçilen bölgelerdeki canlı nesneleri uygulama çalışırken taşıyabilir. Uygulama eski konumu gösteren bir referansla karşılaştığında barrier mekanizması erişimi doğru adrese yönlendirir.</p>



<p class="wp-block-paragraph">Böylece:</p>



<ul class="wp-block-list">
<li>Heap concurrent biçimde sıkıştırılabilir.</li>



<li>Uzun bir Stop-The-World compaction aşaması önlenebilir.</li>



<li>Büyük canlı veri kümelerinde pause süresinin heap boyutuyla büyümesi engellenebilir.</li>



<li>Ayrı ve uzun süren klasik Full GC ihtiyacı ortadan kaldırılabilir.</li>
</ul>



<h2 class="wp-block-heading">Large heap sistemlerde ZGC’nin mimari avantajları</h2>



<h3 class="wp-block-heading">1. Duraklama süresi heap boyutuyla ölçeklenmez</h3>



<p class="wp-block-paragraph">G1’de concurrent marking heap genelinde çalışsa da canlı nesnelerin tahliyesi ve collection set işleme gibi önemli işler Stop-The-World aşamalarında yapılır. Heap ve canlı veri büyüdükçe bu aşamaların kuyruk gecikmesine etkisi artabilir.</p>



<p class="wp-block-paragraph">ZGC’de pahalı marking ve relocation işleri concurrent yürütüldüğü için 32 GB’tan 256 GB’a geçiş, GC pause süresini aynı oranda büyütmez. Büyük in-memory cache, risk hesaplama motoru, fiyatlama sistemi, oyun sunucusu veya gerçek zamanlı analitik platformlarında bu özellik belirleyicidir.</p>



<h3 class="wp-block-heading">2. Concurrent compaction</h3>



<p class="wp-block-paragraph">Low-pause collector’ların yalnızca erişilemeyen nesneleri bulması yetmez; fragmentation sorununu da uzun duraklama üretmeden çözmesi gerekir. ZGC’nin concurrent relocation yaklaşımı bellek sıkıştırmayı uygulama çalışırken gerçekleştirebilir.</p>



<h3 class="wp-block-heading">3. Daha düşük tail-latency riski</h3>



<p class="wp-block-paragraph">Ortalama yanıt süresi iyi olsa bile uzun bir Full GC p99.9 değerini bozabilir. ZGC’nin tasarımı bu tür büyük GC kaynaklı sıçramaları azaltmaya yöneliktir.</p>



<p class="wp-block-paragraph">Bununla birlikte “GC pause düştü” sonucu, uçtan uca latency’nin otomatik olarak düştüğü anlamına gelmez. Concurrent GC thread’leri CPU üzerinde uygulamayla rekabet ederse servis süresi artabilir. Bu nedenle GC pause, CPU utilization ve request latency birlikte izlenmelidir.</p>



<h3 class="wp-block-heading">4. Allocation spike’larına karşı heap headroom</h3>



<p class="wp-block-paragraph">ZGC concurrent çalıştığı için GC bir cycle’ı tamamlayana kadar uygulama yeni nesne ayırmaya devam eder. Maksimum heap; canlı veri kümesini, cycle boyunca yapılacak allocation’ları ve trafik sıçramalarını karşılayacak boşluğu içermelidir.</p>



<p class="wp-block-paragraph">Örneğin canlı veri kümesi 40 GB olan bir servise yalnızca 42 GB heap vermek, teorik olarak yeterli görünse de yüksek allocation rate altında allocation stall yaratabilir. ZGC tuning’in en önemli parametresi çoğu zaman karmaşık bir bayrak değil, doğru <code>-Xmx</code> ve yeterli headroom’dur.</p>



<h3 class="wp-block-heading">5. Adaptif generational yönetim</h3>



<p class="wp-block-paragraph">Modern ZGC, generational yapıda nesil boyutlarını, GC thread sayısını ve tenuring eşiklerini iş yüküne göre dinamik biçimde ayarlar. Bu nedenle başlangıç yaklaşımı az sayıda bayrak kullanmak ve yalnızca ölçüm sonucunda müdahale etmek olmalıdır.</p>



<h2 class="wp-block-heading">G1 GC ve ZGC çalışma mekanizmalarının karşılaştırması</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><th>Teknik özellik</th><th>G1 GC</th><th>ZGC</th></tr><tr><td>Temel hedef</td><td>Throughput ve makul pause süresi dengesi</td><td>Ultra düşük GC pause ve ölçeklenebilirlik</td></tr><tr><td>Çalışma modeli</td><td>Mostly concurrent</td><td>Pahalı GC işlerinde fully concurrent yaklaşım</td></tr><tr><td>Heap organizasyonu</td><td>Region tabanlı, generational</td><td>Region tabanlı, JDK 24+ yalnızca generational</td></tr><tr><td>Marking</td><td>Büyük ölçüde concurrent; başlangıç, remark ve cleanup duraklamaları içerir</td><td>Büyük ölçüde concurrent, kısa faz geçişleri içerir</td></tr><tr><td>Nesne taşıma</td><td>Collection set evacuation işlemi Stop-The-World</td><td>Relocation büyük ölçüde concurrent</td></tr><tr><td>Referans takibi</td><td>Card table, remembered set ve write barrier</td><td>Colored pointers ile load/store barrier’lar</td></tr><tr><td>Pause hedefi</td><td><code>MaxGCPauseMillis</code> ile ergonomik hedef</td><td>Heap boyutundan bağımsız, çok kısa pause tasarımı</td></tr><tr><td>Full GC riski</td><td>Allocation veya evacuation failure durumunda uzun Full GC oluşabilir</td><td>Klasik uzun Full GC ihtiyacını concurrent relocation ile önlemeyi amaçlar</td></tr><tr><td>Throughput</td><td>Genellikle daha yüksek</td><td>Concurrent çalışma ve barrier’lar nedeniyle daha düşük olabilir</td></tr><tr><td>CPU gereksinimi</td><td>Dengeli; pause sırasında yüksek paralellik</td><td>GC cycle boyunca uygulamayla paylaşılacak CPU headroom ister</td></tr><tr><td>Heap headroom ihtiyacı</td><td>Orta; reserve alan yine kritiktir</td><td>Concurrent cycle sırasında allocation için daha yüksek boşluk gerekebilir</td></tr><tr><td>Large heap uygunluğu</td><td>Onlarca GB ve üzeri heap’lerde kullanılabilir</td><td>Yüzlerce GB ve TB ölçeğinde özellikle güçlüdür</td></tr><tr><td>Tuning yaklaşımı</td><td>Pause hedefi, IHOP, reserve ve region davranışı incelenebilir</td><td>Öncelikle heap boyutu ve allocation headroom ayarlanır</td></tr><tr><td>Operasyonel olgunluk</td><td>Uzun süredir varsayılan ve yaygın</td><td>Üretime hazır; sürüme özgü generational geçişi dikkate alınmalı</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Prod ortamında GC seçim kriterleri</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Kriter</td><td>G1 GC lehine durum</td><td>ZGC lehine durum</td><td>Ölçülmesi gereken metrik</td></tr><tr><td>Gecikme SLO’su</td><td>50–300 ms düzeyindeki GC duraklamaları tolere edilebiliyor</td><td>Tek haneli milisaniyelik tail-latency kritik</td><td>p99, p99.9, maksimum pause</td></tr><tr><td>Heap büyüklüğü</td><td>Orta ve büyük heap</td><td>Çok büyük heap veya yüksek live-set</td><td>Heap occupancy, live-set</td></tr><tr><td>Throughput</td><td>Maksimum işlem hacmi öncelikli</td><td>Bir miktar throughput maliyeti kabul edilebilir</td><td>İşlem/saniye, CPU/request</td></tr><tr><td>Allocation rate</td><td>Düşük veya orta</td><td>Yüksek fakat yeterli CPU ve heap headroom mevcut</td><td>MB/s veya GB/s allocation</td></tr><tr><td>CPU kapasitesi</td><td>CPU limiti sıkı</td><td>Concurrent GC için rezerv CPU bulunuyor</td><td>CPU saturation, steal time</td></tr><tr><td>Bellek maliyeti</td><td>Sıkı bellek limiti</td><td>Latency için daha fazla headroom ayrılabilir</td><td>RSS, committed heap, native memory</td></tr><tr><td>Trafik yapısı</td><td>Öngörülebilir ve dengeli</td><td>Ani allocation artışları yaşanıyor</td><td>Allocation spike, burst süresi</td></tr><tr><td>Büyük nesneler</td><td>Humongous allocation düşük</td><td>Büyük nesneler G1’de fragmentation yaratıyor</td><td>Humongous region sayısı</td></tr><tr><td>Konteyner limiti</td><td>Küçük ve sıkı pod limitleri</td><td>Büyük, CPU garantili instance veya pod</td><td>cgroup memory/CPU limitleri</td></tr><tr><td>Operasyonel deneyim</td><td>Ekip G1 loglarını ve davranışını iyi tanıyor</td><td>Ekip ZGC benchmark ve kapasite testlerini yapabiliyor</td><td>Incident ve tuning tecrübesi</td></tr><tr><td>JDK sürümü</td><td>Eski veya kurumsal olarak standartlaştırılmış sürüm</td><td>Güncel LTS veya JDK 24+ kullanılabiliyor</td><td>Dağıtım, sürüm ve flag uyumluluğu</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Temel JVM bayrakları ve tuning örnekleri</h2>



<p class="wp-block-paragraph">Aşağıdaki değerler doğrudan üretime kopyalanacak reçeteler değildir. Heap büyüklüğü, CPU sayısı, NUMA yapısı, container limitleri ve allocation profili test edilmeden kesinleştirilmemelidir.</p>



<h3 class="wp-block-heading">G1 GC için güvenli başlangıç profili</h3>



<pre class="wp-block-code"><code>java \
  -XX:+UseG1GC \
  -Xms32g \
  -Xmx32g \
  -XX:MaxGCPauseMillis=100 \
  -XX:+AlwaysPreTouch \
  -Xlog:gc*,safepoint:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=100M \
  -jar application.jar</code></pre>



<p class="wp-block-paragraph">Bayrakların amacı:</p>



<ul class="wp-block-list">
<li><code>-XX:+UseG1GC</code>: G1 collector’ı etkinleştirir.</li>



<li><code>-Xms32g -Xmx32g</code>: Heap resizing ve çalışma anındaki sayfa tahsis sürprizlerini azaltır.</li>



<li><code>-XX:MaxGCPauseMillis=100</code>: G1’e 100 ms’lik ergonomik pause hedefi verir.</li>



<li><code>-XX:+AlwaysPreTouch</code>: Heap sayfalarını başlangıçta fiziksel belleğe dokundurur; açılışı uzatabilir fakat runtime latency varyansını azaltabilir.</li>



<li><code>-Xlog:gc*,safepoint</code>: GC ve safepoint davranışını analiz etmeyi sağlar.</li>
</ul>



<p class="wp-block-paragraph">G1 için young generation’ı <code>-Xmn</code> ile sabitlemek genellikle önerilmez. G1, pause hedefine ulaşmak için young generation boyutunu adaptif biçimde kullanır; sabit young generation bu kontrol mekanizmasını zayıflatabilir. Oracle’ın önerisi, önce yalnızca heap boyutu ve gerekiyorsa pause hedefiyle başlamaktır. <a href="https://docs.oracle.com/en/java/javase/22/gctuning/garbage-first-garbage-collector-tuning.html">Oracle G1 tuning rehberi</a></p>



<h3 class="wp-block-heading">G1’de Full GC riski görülüyorsa</h3>



<p class="wp-block-paragraph">GC loglarında <code>Pause Full (G1 Compaction Pause)</code> veya evacuation failure görülmesi durumunda aşağıdaki seçenekler kontrollü olarak değerlendirilebilir:</p>



<pre class="wp-block-code"><code>-XX:G1ReservePercent=20
-XX:ConcGCThreads=8</code></pre>



<p class="wp-block-paragraph"><code>G1ReservePercent</code>, allocation ve evacuation için daha fazla güvenlik alanı bırakabilir. <code>ConcGCThreads</code>, marking’in allocation hızına yetişemediği doğrulandıysa artırılabilir. Her ikisi de maliyetsiz değildir: kullanılabilir heap alanını veya uygulamaya kalan CPU’yu azaltabilir.</p>



<p class="wp-block-paragraph">Adaptive IHOP tahminleri iş yükündeki ani değişikliklere yetişemiyorsa, son çare olarak işaretlemenin daha erken başlatılması denenebilir:</p>



<pre class="wp-block-code"><code>-XX:-G1UseAdaptiveIHOP
-XX:InitiatingHeapOccupancyPercent=30</code></pre>



<p class="wp-block-paragraph">Bu ayar statik davranış üretir. Sezonsal veya burst karakterli iş yüklerinde adaptif mekanizmadan daha kötü sonuç verebileceği için uzun süreli karşılaştırmalı test gerektirir.</p>



<h3 class="wp-block-heading">Çok sayıda yinelenen String için</h3>



<p class="wp-block-paragraph">Heap dump ve profiler, yüksek miktarda yinelenen <code>String</code> bulunduğunu gösteriyorsa:</p>



<pre class="wp-block-code"><code>-XX:+UseStringDeduplication</code></pre>



<p class="wp-block-paragraph">Bu seçenek bellek tüketimini azaltabilir; ancak concurrent deduplication işleminin CPU maliyeti vardır. Kanıt olmadan varsayılan tuning listesine eklenmemelidir.</p>



<h3 class="wp-block-heading">JDK 24+ ZGC düşük gecikme profili</h3>



<pre class="wp-block-code"><code>java \
  -XX:+UseZGC \
  -Xms64g \
  -Xmx64g \
  -XX:+AlwaysPreTouch \
  -Xlog:gc*,safepoint:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=100M \
  -jar application.jar</code></pre>



<p class="wp-block-paragraph"><code>-Xms</code> ile <code>-Xmx</code> değerlerinin eşitlenmesi:</p>



<ul class="wp-block-list">
<li>Heap’in çalışma sırasında büyüme ihtiyacını azaltır.</li>



<li>ZGC’nin belleği işletim sistemine iade etmesini fiilen sınırlar.</li>



<li>Commit/uncommit ve page fault kaynaklı latency varyansını azaltabilir.</li>



<li>Başlangıç süresini ve kalıcı bellek tüketimini artırır.</li>
</ul>



<p class="wp-block-paragraph">Ultra düşük gecikme hedeflerinde bu değiş tokuş çoğu zaman anlamlıdır; bellek yoğunluğu öncelikli ortamlarda ise esnek heap daha uygun olabilir.</p>



<h3 class="wp-block-heading">Esnek bellek kullanan ZGC profili</h3>



<pre class="wp-block-code"><code>java \
  -XX:+UseZGC \
  -Xms16g \
  -Xmx64g \
  -XX:SoftMaxHeapSize=48g \
  -XX:ZUncommitDelay=300 \
  -Xlog:gc*,safepoint:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=100M \
  -jar application.jar</code></pre>



<p class="wp-block-paragraph"><code>SoftMaxHeapSize</code>, ZGC’nin normal koşullarda altında kalmaya çalışacağı yumuşak sınırdır. Collector, allocation stall riskini azaltmak için gerekirse <code>-Xmx</code> değerine kadar büyüyebilir.</p>



<p class="wp-block-paragraph">Örnekte ZGC 48 GB altında kalmaya çalışır; ancak trafik sıçramasında 64 GB’a kadar çıkabilir. Bu, bellek maliyetiyle burst dayanıklılığı arasında kullanışlı bir denge sağlar.</p>



<h3 class="wp-block-heading">JDK 21 ve 22’de Generational ZGC</h3>



<pre class="wp-block-code"><code>java \
  -XX:+UseZGC \
  -XX:+ZGenerational \
  -Xms64g \
  -Xmx64g \
  -XX:+AlwaysPreTouch \
  -jar application.jar</code></pre>



<p class="wp-block-paragraph">Bu örnekteki <code>-XX:+ZGenerational</code> yalnızca ilgili eski sürüm davranışı için kullanılmalıdır. JDK 24 ve sonrasında bu seçenek kaldırılmıştır.</p>



<h3 class="wp-block-heading">ZGC’de thread sayısını değiştirmek</h3>



<p class="wp-block-paragraph">ZGC, GC thread sayısını adaptif olarak ayarlayabilir. Bu nedenle <code>-XX:ConcGCThreads</code> başlangıç profilinde sabitlenmemelidir.</p>



<p class="wp-block-paragraph">GC allocation hızına yetişemiyor, allocation stall oluşuyor ve sistemde boş CPU bulunuyorsa kontrollü bir deney yapılabilir:</p>



<pre class="wp-block-code"><code>-XX:ConcGCThreads=12</code></pre>



<p class="wp-block-paragraph">Değer çok düşükse collector geride kalabilir. Çok yüksekse GC, uygulama thread’lerinin CPU’sunu tüketerek request latency ve throughput’u bozabilir.</p>



<h3 class="wp-block-heading">Explicit large pages</h3>



<p class="wp-block-paragraph">Büyük ve sabit heap’lerde, işletim sistemi önceden doğru biçimde hazırlanmışsa:</p>



<pre class="wp-block-code"><code>-XX:+UseLargePages</code></pre>



<p class="wp-block-paragraph">Large pages, TLB baskısını azaltarak throughput ve latency’ye yardımcı olabilir. Ancak host üzerinde yeterli sayıda sayfa ayrılmadan yalnızca JVM bayrağını eklemek yeterli değildir.</p>



<p class="wp-block-paragraph">Transparent Huge Pages ile explicit large pages aynı şey değildir. Oracle, latency-sensitive ZGC sistemlerinde Transparent Huge Pages kullanımının istenmeyen latency sıçramaları oluşturabileceğini belirtmektedir. Bu nedenle kernel ayarı, JVM konfigürasyonu ve gerçek workload birlikte test edilmelidir. <a href="https://docs.oracle.com/en/java/javase/24/gctuning/z-garbage-collector.html">Oracle ZGC large pages açıklaması</a></p>



<h2 class="wp-block-heading">GC loglarında hangi sinyaller izlenmeli?</h2>



<p class="wp-block-paragraph">Collector seçiminden bağımsız olarak aşağıdaki metrikler birlikte değerlendirilmelidir:</p>



<ul class="wp-block-list">
<li>GC pause p50, p95, p99 ve maksimum değerleri</li>



<li>Young, mixed, old ve full collection sayısı</li>



<li>GC cycle süresi ve cycle sıklığı</li>



<li>Allocation rate</li>



<li>Promotion rate</li>



<li>Collection sonrası heap occupancy</li>



<li>Canlı veri kümesi büyüklüğü</li>



<li>Allocation stall sayısı ve süresi</li>



<li>Application throughput</li>



<li>CPU kullanımı ve CPU throttling</li>



<li>Container memory limiti ile toplam RSS farkı</li>



<li>Safepoint süresi ve safepoint’e ulaşma gecikmesi</li>



<li>Reference processing süresi</li>



<li>G1 humongous region ve evacuation failure sayısı</li>
</ul>



<p class="wp-block-paragraph">Yalnızca “GC toplam süresi” metriğini izlemek yanıltıcıdır. ZGC’de kısa duraklamalara rağmen concurrent GC CPU maliyeti artabilir. G1’de toplam GC süresi makul görünürken tek bir Full GC tail-latency SLO’sunu ihlal edebilir.</p>



<h2 class="wp-block-heading">Üretime geçiş için önerilen test yöntemi</h2>



<p class="wp-block-paragraph">GC değişimi kontrollü bir performans mühendisliği çalışması olarak ele alınmalıdır:</p>



<ol start="1" class="wp-block-list">
<li>Mevcut collector ile temel ölçüm alın.</li>



<li>Aynı JDK dağıtımı, heap ve CPU limitleriyle ikinci collector’ı test edin.</li>



<li>Gerçekçi canlı veri kümesini test başlamadan önce oluşturun.</li>



<li>JVM ısınması ve JIT derlemesi tamamlandıktan sonra ölçüm alın.</li>



<li>Ortalama yükün yanı sıra trafik sıçraması ve uzun süreli soak test uygulayın.</li>



<li>p99.9 latency, throughput, CPU ve RSS değerlerini birlikte karşılaştırın.</li>



<li>Heap dump, JFR ve GC loglarını allocation kaynakları açısından inceleyin.</li>



<li>Canary deployment ile sınırlı üretim trafiğinde doğrulayın.</li>



<li>Otomatik rollback eşiğini GC pause yerine uçtan uca servis SLO’suna bağlayın.</li>
</ol>



<p class="wp-block-paragraph">Konteyner ortamında yalnızca <code>-Xmx</code> değerini pod bellek limitine eşitlemeyin. Metaspace, code cache, thread stack’leri, direct buffer’lar, JNI kütüphaneleri ve JVM’nin native yapıları da RSS tüketir. Heap dışında yeterli güvenlik payı bırakılmalıdır.</p>



<h2 class="wp-block-heading">Hangi senaryoda hangi GC seçilmeli?</h2>



<h3 class="wp-block-heading">G1 GC tercih edin:</h3>



<ul class="wp-block-list">
<li>Gecikme hedefi yüzlerce milisaniye ölçeğindeyse,</li>



<li>Throughput, ultra düşük pause süresinden daha önemliyse,</li>



<li>CPU limiti sıkıysa,</li>



<li>Heap orta veya büyük olsa da canlı veri kümesi yönetilebiliyorsa,</li>



<li>Operasyon ekibi yerleşik ve yaygın bir collector istiyorsa,</li>



<li>Gerçek workload altında G1 Full GC veya evacuation problemi üretmiyorsa.</li>
</ul>



<h3 class="wp-block-heading">ZGC tercih edin:</h3>



<ul class="wp-block-list">
<li>p99.9 ve üzeri tail-latency kritikse,</li>



<li>Tek haneli milisaniyelik servis hedefleri bulunuyorsa,</li>



<li>Heap onlarca veya yüzlerce gigabaytsa,</li>



<li>Canlı veri kümesi büyükse,</li>



<li>Uzun G1 evacuation ya da Full GC duraklamaları görülüyorsa,</li>



<li>Concurrent GC için CPU kapasitesi ayrılabiliyorsa,</li>



<li>Heap’e yeterli allocation headroom verilebiliyorsa.</li>
</ul>



<h2 class="wp-block-heading">Sonuç</h2>



<p class="wp-block-paragraph">G1 GC ve ZGC arasında evrensel bir kazanan yoktur. G1 GC, yüksek throughput ile nispeten kısa ve tahmin edilebilir duraklamalar arasında güçlü bir denge sağlar. Bu nedenle çoğu kurumsal Java servisi için hâlâ iyi bir başlangıç noktasıdır.</p>



<p class="wp-block-paragraph">ZGC’nin ayrıştığı alan, büyük heap ve katı tail-latency hedefleridir. Colored pointers, barrier mekanizmaları, concurrent marking ve concurrent relocation sayesinde pahalı GC işlemlerinin büyük bölümünü uygulamayı durdurmadan gerçekleştirir. Böylece heap büyüklüğünün Stop-The-World süresine etkisini önemli ölçüde azaltır.</p>



<p class="wp-block-paragraph">Bunun karşılığında ZGC’ye yeterli CPU ve heap headroom sağlanmalıdır. Collector seçimi yalnızca maksimum GC pause karşılaştırmasına indirgenmemeli; throughput, p99.9 yanıt süresi, allocation rate, CPU tüketimi, RSS ve altyapı maliyeti birlikte değerlendirilmelidir.</p>



<p class="wp-block-paragraph">Kurumsal üretim ortamı için en güvenilir yaklaşım şudur: Önce sade bir JVM konfigürasyonu kurun, ölçüm alın, üretime benzeyen yük altında G1 ve ZGC’yi karşılaştırın ve yalnızca gözlemlenmiş bir problemi çözen bayrağı ekleyin.</p>
<p><a href="https://alikarahisar.com/blog/garbage-collector-secimi-g1-gc-vs-zgc/">Garbage Collector Seçimi ve Akort Edilmesi: G1 GC vs. ZGC</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alikarahisar.com/blog/garbage-collector-secimi-g1-gc-vs-zgc/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Java Virtual Threads: Daha Fazla Thread Değil, Daha Doğru Bir Eşzamanlılık Modeli</title>
		<link>https://alikarahisar.com/blog/java-virtual-threads/</link>
					<comments>https://alikarahisar.com/blog/java-virtual-threads/#respond</comments>
		
		<dc:creator><![CDATA[Ali Karahisar]]></dc:creator>
		<pubDate>Mon, 31 Aug 2026 12:02:01 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Yazılım]]></category>
		<category><![CDATA[Java Virtual Threads]]></category>
		<guid isPermaLink="false">https://alikarahisar.com/blog/?p=34</guid>

					<description><![CDATA[<p>Java sunucu uygulamalarında yıllardır aynı temel gerilimle yaşadık: Senkron, bloklayan kod kolay anlaşılırdı; fakat yüksek eşzamanlılık altında yeterince ölçeklenmezdi. Non-blocking ve reactive kod ise donanımı verimli kullanabiliyor, ancak uygulamanın kontrol akışını daha karmaşık hâle getiriyordu. Java 21 ile kalıcı hâle gelen Virtual Threads, bu denklemi değiştiriyor. Amaç, bloklayan kodu ortadan kaldırmak değil; bloklayan bir işin [&#8230;]</p>
<p><a href="https://alikarahisar.com/blog/java-virtual-threads/">Java Virtual Threads: Daha Fazla Thread Değil, Daha Doğru Bir Eşzamanlılık Modeli</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Java sunucu uygulamalarında yıllardır aynı temel gerilimle yaşadık: Senkron, bloklayan kod kolay anlaşılırdı; fakat yüksek eşzamanlılık altında yeterince ölçeklenmezdi. Non-blocking ve reactive kod ise donanımı verimli kullanabiliyor, ancak uygulamanın kontrol akışını daha karmaşık hâle getiriyordu.</p>



<span id="more-34"></span>



<p class="wp-block-paragraph">Java 21 ile kalıcı hâle gelen Virtual Threads, bu denklemi değiştiriyor. Amaç, bloklayan kodu ortadan kaldırmak değil; bloklayan bir işin bekleme süresince pahalı bir işletim sistemi thread’ini işgal etmesini önlemek.</p>



<p class="wp-block-paragraph">Bu nedenle Virtual Threads’i yalnızca “daha hafif thread” şeklinde tanımlamak eksik kalır. Asıl mimari değişiklik şudur:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Uygulamanın eşzamanlılık birimi yeniden <code>Thread</code> ile temsil edilebilir; fakat her Java thread’i için ayrı bir işletim sistemi thread’i ayırmak gerekmez.</p>
</blockquote>



<h2 class="wp-block-heading">Platform Thread ve Virtual Thread arasındaki fark</h2>



<p class="wp-block-paragraph">Geleneksel Java thread’leri, yani platform thread’leri, işletim sistemi thread’leri üzerine kuruludur. Basitleştirilmiş olarak ilişki 1:1’dir:</p>



<pre class="wp-block-code"><code>Java Platform Thread ─── OS Thread</code></pre>



<p class="wp-block-paragraph">Bir platform thread ağdan veri, veritabanından yanıt veya dosya sisteminden sonuç beklerken işletim sistemi thread’i de ona bağlı kalır. Thread pool kullanmak thread oluşturma maliyetini azaltır; fakat aynı anda çalışabilecek iş sayısını havuz büyüklüğüyle sınırlar.</p>



<p class="wp-block-paragraph">Virtual Thread mimarisinde ise ilişki M biçimindedir:</p>



<pre class="wp-block-code"><code>Çok sayıda Virtual Thread
            │
            ▼
Az sayıda Carrier Platform Thread
            │
            ▼
       OS Thread’leri</code></pre>



<p class="wp-block-paragraph">Virtual thread çalışacağı zaman JVM tarafından bir platform thread’e, yani <em>carrier thread</em>’e bağlanır. Desteklenen bir bloklayan I/O çağrısına geldiğinde virtual thread askıya alınabilir ve carrier serbest bırakılabilir. Aynı carrier bu sırada başka bir virtual thread’i çalıştırır.</p>



<p class="wp-block-paragraph">I/O tamamlandığında virtual thread yeniden çalıştırılabilir; üstelik daha önce kullandığı carrier’a dönmek zorunda değildir. Uygulama açısından ise bu geçiş görünmezdir:</p>



<pre class="wp-block-code"><code>var response = httpClient.send(request, BodyHandlers.ofString());</code></pre>



<p class="wp-block-paragraph">Kod hâlâ senkron görünür. Metot sonucu bekler, normal <code>try/catch</code> kullanılabilir ve stack trace istek akışını korur. Fakat bekleyen virtual thread uygun durumda carrier’ı işgal etmez.</p>



<p class="wp-block-paragraph">Platform ve virtual thread’leri şöyle karşılaştırabiliriz:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><th>Özellik</th><th>Platform Thread</th><th>Virtual Thread</th></tr><tr><td>OS thread ilişkisi</td><td>Yaklaşık 1:1</td><td>M</td></tr><tr><td>Oluşturma maliyeti</td><td>Görece yüksek</td><td>Çok düşük</td></tr><tr><td>Tipik kullanım</td><td>Uzun ömürlü worker pool</td><td>Her görev için yeni thread</td></tr><tr><td>Bloklayan I/O</td><td>OS thread’i işgal eder</td><td>Çoğu durumda carrier’dan ayrılır</td></tr><tr><td>Stack</td><td>Native bellekte, görece büyük</td><td>Heap üzerinde büyüyüp küçülen stack chunk’ları</td></tr><tr><td>Uygun iş yükü</td><td>CPU ve sınırlı eşzamanlılık</td><td>Çok yüksek I/O eşzamanlılığı</td></tr><tr><td>Havuzlama</td><td>Genellikle gerekir</td><td>Yapılmamalıdır</td></tr><tr><td>Programlama modeli</td><td>Senkron</td><td>Senkron</td></tr><tr><td>Temel kazanç</td><td>Kontrollü kaynak kullanımı</td><td>Yüksek throughput ve basit kontrol akışı</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Virtual Threads Java 21’de JEP 444 ile kalıcı özellik hâline gelmiştir. OpenJDK’nın temel hedefi de thread-per-request biçimindeki sunucu uygulamalarının daha verimli ölçeklenmesini sağlamaktır. <a href="https://openjdk.org/jeps/444">JEP 444: Virtual Threads</a></p>



<h2 class="wp-block-heading">Thread-Per-Request modeline neden geri dönüyoruz?</h2>



<p class="wp-block-paragraph">Thread-per-request yaklaşımında her istek başından sonuna kadar tek bir mantıksal thread üzerinde ilerler:</p>



<pre class="wp-block-code"><code>HTTP isteği
   → kimlik doğrulama
   → veritabanı sorgusu
   → uzak servis çağrısı
   → yanıt üretimi</code></pre>



<p class="wp-block-paragraph">Bu modelin güçlü tarafı yapısal sadeliğidir:</p>



<ul class="wp-block-list">
<li>Kontrol akışı yukarıdan aşağı okunur.</li>



<li>Yerel değişkenler istek durumunu doğal biçimde taşır.</li>



<li>Hatalar <code>try/catch/finally</code> ile yönetilir.</li>



<li>Transaction ve güvenlik sınırları daha kolay görülür.</li>



<li>Debugger ile adım adım ilerlenebilir.</li>



<li>Stack trace, iş çağrı zincirini doğrudan gösterir.</li>
</ul>



<p class="wp-block-paragraph">Sorun modelin kendisi değil, modelin platform thread’leriyle uygulanmasıydı.</p>



<p class="wp-block-paragraph">Little Yasası’na göre sistemde aynı anda bulunan ortalama iş sayısı yaklaşık olarak throughput ile ortalama gecikmenin çarpımıdır:</p>



<pre class="wp-block-code"><code>Concurrency ≈ Throughput × Latency</code></pre>



<p class="wp-block-paragraph">Örneğin ortalama 200 ms süren bir işlemde saniyede 5.000 istek karşılamak istiyorsak sistemin yaklaşık 1.000 isteği eşzamanlı taşıması gerekir. Her istek bir platform thread tüketiyorsa thread sayısı kısa sürede temel darboğaza dönüşebilir.</p>



<p class="wp-block-paragraph">Reactive ve callback tabanlı sistemler bu problemi, bekleme sırasında thread’i serbest bırakarak çözdü. Bunun karşılığında uygulamanın mantıksal çağrı zinciri callback, <code>Mono</code>, <code>Flux</code> veya <code>CompletableFuture</code> aşamalarına bölündü.</p>



<p class="wp-block-paragraph">Virtual Threads aynı kaynak sorununa farklı bir katmanda çözüm getirir:</p>



<ul class="wp-block-list">
<li>Reactive yaklaşım, thread paylaşımını uygulama programlama modeline taşır.</li>



<li>Virtual Threads ise thread paylaşımını JVM zamanlayıcısının sorumluluğuna bırakır.</li>
</ul>



<p class="wp-block-paragraph">Böylece “bir istek, bir thread” modeli yeniden ekonomik hâle gelir. Buradaki thread artık pahalı bir OS kaynağı değil, uygulamadaki bir görevi temsil eden hafif bir Java nesnesidir.</p>



<p class="wp-block-paragraph">Önemli bir sonuç da şudur: Virtual Threads tek bir isteği kendiliğinden hızlandırmaz. Esas fayda, çok sayıda bekleyen işi daha az platform thread ile taşıyarak toplam throughput’u artırmaktır. I/O gecikmesi yine aynı gecikmedir.</p>



<h2 class="wp-block-heading">Reactive programming artık gereksiz mi?</h2>



<p class="wp-block-paragraph">Kısa cevap: Hayır. Fakat reactive programlamanın seçim gerekçesi değişmiştir.</p>



<p class="wp-block-paragraph">Reactive stack’lerin yaygınlaşmasındaki önemli nedenlerden biri, çok sayıda bloklayan isteği sınırlı platform thread havuzuyla taşımanın zor olmasıydı. Virtual Threads bu teknik zorunluluğu önemli ölçüde azaltır.</p>



<p class="wp-block-paragraph">Tipik bir servis aşağıdaki özelliklere sahipse Spring MVC ve Virtual Threads güçlü bir seçenek olabilir:</p>



<ul class="wp-block-list">
<li>Request/response ağırlıklı çalışma</li>



<li>JDBC gibi bloklayan istemciler</li>



<li>Senkron HTTP çağrıları</li>



<li>Kısa veya orta uzunlukta iş akışları</li>



<li>Ekibin imperative Java konusunda daha deneyimli olması</li>



<li>Kolay debugging ve okunabilir stack trace beklentisi</li>
</ul>



<p class="wp-block-paragraph">Spring Boot 3.2’den itibaren Java 21 üzerinde virtual thread desteği şu ayarla etkinleştirilebilir:</p>



<pre class="wp-block-code"><code>spring.threads.virtual.enabled=true</code></pre>



<p class="wp-block-paragraph">Spring ekibi de Spring MVC ile Virtual Threads kombinasyonunu geleneksel web uygulamaları için yalın bir seçenek olarak konumlandırmaktadır. Kaynak: <a href="https://spring.io/blog/2023/10/16/runtime-efficiency-with-spring/" target="_blank" rel="noreferrer noopener">Spring: Runtime Efficiency with Spring</a></p>



<p class="wp-block-paragraph">Bununla birlikte WebFlux ve Reactive Streams hâlâ belirgin avantajlara sahiptir:</p>



<ul class="wp-block-list">
<li>Sonsuz veya uzun süreli veri akışları</li>



<li>Backpressure’ın uçtan uca ifade edilmesi</li>



<li>Server-Sent Events ve streaming response’lar</li>



<li>Çok aşamalı event pipeline’ları</li>



<li>Deklaratif dönüşüm ve birleştirme operasyonları</li>



<li>Reactive sürücüler üzerine kurulmuş mevcut sistemler</li>



<li>Akışın talep, iptal ve hata sinyallerinin bir protokol olarak modellenmesi</li>
</ul>



<p class="wp-block-paragraph">Virtual Threads bir zamanlama mekanizmasıdır; backpressure protokolü değildir. Tüketici üreticiden yavaşsa virtual thread kullanmak tek başına kuyruk büyümesini veya bellek baskısını önlemez.</p>



<p class="wp-block-paragraph">Dolayısıyla doğru soru “WebFlux mü, Virtual Threads mü?” değildir. Daha doğru karar çerçevesi şöyledir:</p>



<ul class="wp-block-list">
<li>Yüksek sayıda bağımsız, bloklayan request/response işi için imperative kod ve Virtual Threads genellikle daha basittir.</li>



<li>Akış semantiği, backpressure veya yoğun reaktif kompozisyon gerekiyorsa WebFlux değerini korur.</li>



<li>Mevcut reactive sistem yalnızca virtual thread geldi diye yeniden yazılmamalıdır.</li>



<li>Yeni bir CRUD servisi de sırf yüksek eşzamanlılık ihtimali var diye otomatik olarak reactive tasarlanmamalıdır.</li>
</ul>



<h2 class="wp-block-heading">I/O yoğunluklu bir örnek</h2>



<p class="wp-block-paragraph">Aşağıdaki servis, çok sayıda sipariş için iki uzak sistemden bloklayan HTTP çağrıları yapıyor. Her sipariş ayrı bir virtual thread üzerinde işleniyor.</p>



<pre class="wp-block-code language-java"><code>import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public final class OrderAggregator {

    private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(2))
            .build();

    /*
     * Virtual thread sayısını değil, kapasitesi sınırlı olan uzak servise
     * yapılan eşzamanlı çağrı sayısını sınırlandırıyoruz.
     */
    private static final Semaphore REMOTE_SERVICE_LIMIT =
            new Semaphore(100);

    public static void main(String&#91;] args) throws Exception {
        List&lt;Long&gt; orderIds = java.util.stream.LongStream
                .rangeClosed(1, 1_000)
                .boxed()
                .toList();

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            List&lt;Callable&lt;OrderView&gt;&gt; tasks = orderIds.stream()
                    .&lt;Callable&lt;OrderView&gt;&gt;map(orderId -&gt;
                            () -&gt; loadOrderView(orderId))
                    .toList();

            var futures = executor.invokeAll(tasks);

            for (var future : futures) {
                OrderView view = future.get();
                System.out.println(view);
            }
        }
    }

    private static OrderView loadOrderView(long orderId)
            throws IOException, InterruptedException {

        String orderJson = get(
                "https://orders.example.com/orders/" + orderId);

        String shipmentJson = get(
                "https://shipping.example.com/shipments/" + orderId);

        return new OrderView(orderId, orderJson, shipmentJson);
    }

    private static String get(String url)
            throws IOException, InterruptedException {

        REMOTE_SERVICE_LIMIT.acquire();
        try {
            HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                    .timeout(Duration.ofSeconds(3))
                    .GET()
                    .build();

            return HTTP_CLIENT.send(
                    request,
                    HttpResponse.BodyHandlers.ofString()
            ).body();
        } finally {
            REMOTE_SERVICE_LIMIT.release();
        }
    }

    record OrderView(
            long orderId,
            String orderJson,
            String shipmentJson
    ) {}
}</code></pre>



<p class="wp-block-paragraph">Kodun önemli tarafı, çağrıların bloklayan <code>HttpClient.send()</code> kullanmasına rağmen her görev için ayrı bir virtual thread oluşturulabilmesidir. I/O bekleyen görevler uygun olduğunda carrier thread’i serbest bırakır.</p>



<p class="wp-block-paragraph">Burada executor bir virtual thread havuzu değildir. <code>newVirtualThreadPerTaskExecutor()</code> her görev için yeni bir virtual thread oluşturur. Virtual thread’ler ucuz olduğundan yeniden kullanılmaları veya sabit boyutlu bir havuzda tutulmaları amaçlanmaz.</p>



<p class="wp-block-paragraph">Buna karşılık uzak servis bağlantıları sınırsız değildir. Bu nedenle <code>Semaphore</code>, iş parçacığı sayısını değil kıt kaynağa erişimi sınırlar. OpenJDK da virtual thread’lerin havuzlanmamasını ve sınırlı kaynakların semaphore gibi mekanizmalarla korunmasını önerir. <a href="https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html">Oracle Virtual Threads Adoption Guide</a></p>



<p class="wp-block-paragraph">Üretim kodunda ayrıca timeout, retry bütçesi, circuit breaker, rate limit ve iptal davranışı açık biçimde tasarlanmalıdır. Bir milyon virtual thread oluşturabilmek, veritabanının bir milyon bağlantıyı veya uzak servisin bir milyon eşzamanlı isteği kaldırabileceği anlamına gelmez.</p>



<h2 class="wp-block-heading">Kaçınılması gereken antipattern’ler</h2>



<h3 class="wp-block-heading">1. Virtual thread’leri sabit boyutlu havuzda toplamak</h3>



<p class="wp-block-paragraph">Yanlış yaklaşım:</p>



<pre class="wp-block-code"><code>var executor = Executors.newFixedThreadPool(
        100,
        Thread.ofVirtual().factory()
);</code></pre>



<p class="wp-block-paragraph">Bu tasarım virtual thread’in temel avantajını yapay olarak sınırlar. Bir göreve bir virtual thread oluşturulmalıdır:</p>



<pre class="wp-block-code"><code>var executor = Executors.newVirtualThreadPerTaskExecutor();</code></pre>



<p class="wp-block-paragraph">Eşzamanlılık sınırı gerekiyorsa thread sayısı yerine veritabanı, haricî API veya başka bir kıt kaynak doğrudan sınırlandırılmalıdır.</p>



<h3 class="wp-block-heading">2. Uzun süren CPU işlerini sınırsız virtual thread’e dağıtmak</h3>



<p class="wp-block-paragraph">Virtual Threads CPU kapasitesini artırmaz. Binlerce CPU-bound görevi virtual thread üzerinde başlatmak çekirdek sayısını değiştirmez; yalnızca zamanlama baskısı ve kuyruk oluşturur.</p>



<p class="wp-block-paragraph">CPU-bound işlerde çekirdek sayısına göre boyutlandırılmış executor, <code>ForkJoinPool</code> veya uygun paralel algoritma çoğu zaman daha doğrudur.</p>



<pre class="wp-block-code"><code>I/O-bound görev  → Virtual Threads için güçlü aday
CPU-bound görev  → Sınırlı paralellik gerekir</code></pre>



<h3 class="wp-block-heading">3. JDK 21–23 üzerinde bloklayan I/O’yu <code>synchronized</code> içinde yapmak</h3>



<p class="wp-block-paragraph">Java 21–23’te virtual thread, <code>synchronized</code> bir bölüm içinde bloklandığında carrier thread’e pinlenebilir:</p>



<pre class="wp-block-code"><code>synchronized (lock) {
    callSlowRemoteService();
}</code></pre>



<p class="wp-block-paragraph">Bu durum programı hatalı yapmaz; fakat uzun ve sık gerçekleşiyorsa ölçeklenebilirliği düşürür. Bu JDK sürümlerinde gerekli noktalarda <code>ReentrantLock</code> kullanılabilir veya I/O kritik bölümün dışına taşınabilir:</p>



<pre class="wp-block-code"><code>lock.lock();
try {
    updateSharedInMemoryState();
} finally {
    lock.unlock();
}

callSlowRemoteService();</code></pre>



<p class="wp-block-paragraph">Ancak bu tavsiye sürüme bağlıdır. JDK 24’te gelen JEP 491 ile <code>synchronized</code> altında bekleyen virtual thread’lerin carrier’ı serbest bırakabilmesi sağlandı. Dolayısıyla JDK 24+ üzerinde sırf virtual thread kullanılıyor diye bütün <code>synchronized</code> bloklarını mekanik olarak <code>ReentrantLock</code> ile değiştirmek doğru değildir. <a href="https://docs.oracle.com/en/java/javase/24/migrate/significant-changes-jdk-24.html">Oracle JDK 24 değişiklikleri</a></p>



<p class="wp-block-paragraph">Yine de sürümden bağımsız tasarım ilkesi geçerlidir: Ağ veya veritabanı çağrısı boyunca lock tutmak contention, uzun kuyruklar ve deadlock riski yaratabilir. Ayrıca native veya foreign-function çağrıları güncel JDK’larda hâlâ pinning oluşturabilir.</p>



<h3 class="wp-block-heading">4. <code>ThreadLocal</code> içinde pahalı nesne cache’lemek</h3>



<p class="wp-block-paragraph">Virtual thread’ler <code>ThreadLocal</code> destekler. Sorun API’nin çalışmaması değil, thread sayısının artık çok büyük olabilmesidir:</p>



<pre class="wp-block-code"><code>private static final ThreadLocal&lt;ExpensiveParser&gt; PARSER =
        ThreadLocal.withInitial(ExpensiveParser::new);</code></pre>



<p class="wp-block-paragraph">On binlerce veya yüz binlerce virtual thread, aynı sayıda pahalı nesne örneği oluşturabilir. Platform thread havuzunda makul görünen “thread başına cache” yaklaşımı virtual thread mimarisinde ciddi bellek tüketimine dönüşebilir.</p>



<p class="wp-block-paragraph">Daha güvenli seçenekler şunlardır:</p>



<ul class="wp-block-list">
<li>Immutable ve thread-safe nesneleri paylaşmak</li>



<li>Gerçekten sınırlı kaynaklar için açık bir pool kullanmak</li>



<li>Request context’i mümkünse metot parametreleriyle taşımak</li>



<li>Kullanılan JDK sürümünün durumuna göre <code>ScopedValue</code> değerlendirmek</li>



<li>Framework’lerin context propagation davranışını test etmek</li>
</ul>



<p class="wp-block-paragraph">Correlation ID veya güvenlik bağlamı gibi küçük request-scoped veriler <code>ThreadLocal</code> ile çalışabilir. Ancak bellek maliyeti, yaşam süresi, temizleme davranışı ve <code>InheritableThreadLocal</code> ile istemsiz veri aktarımı özellikle incelenmelidir.</p>



<h3 class="wp-block-heading">5. Sınırsız dış kaynak tüketimini ölçeklenebilirlik sanmak</h3>



<p class="wp-block-paragraph">Virtual thread sayısı arttığında darboğaz ortadan kalkmaz; başka bir katmana taşınır:</p>



<ul class="wp-block-list">
<li>JDBC connection pool</li>



<li>HTTP connection pool</li>



<li>Uzak servis kapasitesi</li>



<li>Rate limit</li>



<li>Heap</li>



<li>Dosya tanıtıcıları</li>



<li>CPU</li>



<li>Kuyruklar</li>
</ul>



<p class="wp-block-paragraph">Bu nedenle virtual thread geçişi mutlaka bulkhead, timeout ve yük testi stratejisiyle birlikte ele alınmalıdır.</p>



<h3 class="wp-block-heading">6. Yalnızca ortalama latency’ye bakmak</h3>



<p class="wp-block-paragraph">Virtual Threads’e geçiş değerlendirilirken şu metrikler birlikte izlenmelidir:</p>



<ul class="wp-block-list">
<li>Throughput</li>



<li>p95 ve p99 latency</li>



<li>Carrier thread kullanımı</li>



<li>CPU ve heap tüketimi</li>



<li>GC davranışı</li>



<li>Bağlantı havuzu bekleme süreleri</li>



<li>Uzak servis hata oranları</li>



<li>Virtual thread pinning olayları</li>



<li>Kuyruk uzunlukları</li>
</ul>



<p class="wp-block-paragraph">Java Flight Recorder, özellikle pinning ve virtual thread yaşam döngüsüyle ilgili olayları gözlemlemek için kullanılabilir. Java 21 dokümantasyonu <code>jdk.VirtualThreadPinned</code> olayını ve <code>jdk.tracePinnedThreads</code> seçeneğini açıklar. JDK 24+ değerlendirmelerinde ise monitor pinning davranışının değiştiği unutulmamalıdır.</p>



<h2 class="wp-block-heading">Mimari karar özeti</h2>



<p class="wp-block-paragraph">Virtual Threads, reactive programlamanın yerini alan evrensel bir teknoloji değildir. Daha çok, reactive programlamanın zorunlu hâle gelmesine neden olan thread kıtlığını JVM seviyesinde azaltır.</p>



<p class="wp-block-paragraph">İyi bir başlangıç matrisi şöyledir:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Senaryo</td><td>Muhtemel tercih</td></tr><tr><td>JDBC ve senkron HTTP kullanan CRUD servisi</td><td>Spring MVC + Virtual Threads</td></tr><tr><td>Çok sayıda bağımsız I/O çağrısı</td><td>Virtual Threads</td></tr><tr><td>CPU yoğun hesaplama</td><td>Sınırlı platform thread paralelliği</td></tr><tr><td>Sürekli veya sonsuz event stream</td><td>WebFlux / Reactive Streams</td></tr><tr><td>Uçtan uca backpressure</td><td>Reactive yaklaşım</td></tr><tr><td>Mevcut, çalışan reactive mimari</td><td>Yeniden yazmak için tek başına gerekçe yok</td></tr><tr><td>Basit kontrol akışı ve kolay debugging önceliği</td><td>Virtual Threads</td></tr><tr><td>Karma model</td><td>Kontrollü biçimde MVC, virtual thread ve reactive istemcilerin birlikte kullanımı</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Sonuç</h2>



<p class="wp-block-paragraph">Project Loom’un en önemli katkısı “milyonlarca thread oluşturabilmek” değildir. Daha temel katkısı, Java’daki mantıksal görev ile işletim sistemi thread’i arasındaki zorunlu 1:1 ilişkiyi kaldırmasıdır.</p>



<p class="wp-block-paragraph">Bu değişiklik sayesinde:</p>



<ul class="wp-block-list">
<li>Thread-per-request modeli yeniden ölçeklenebilir hâle gelir.</li>



<li>Bloklayan ve okunabilir Java kodu yüksek I/O eşzamanlılığı taşıyabilir.</li>



<li>Stack trace, debugging ve hata yönetimi doğal yapısını korur.</li>



<li>Reactive programlama teknik bir zorunluluk olmaktan çıkarak bilinçli bir mimari seçeneğe dönüşür.</li>
</ul>



<p class="wp-block-paragraph">Fakat fiziksel kaynak sınırları ortadan kalkmaz. Virtual thread’ler sınırsız veritabanı bağlantısı, sınırsız CPU veya sınırsız uzak servis kapasitesi sağlamaz. Başarılı bir geçiş; thread sayısını büyütmekten çok, darboğazları doğru yerde sınırlandırmayı, kullanılan kütüphaneleri doğrulamayı ve sistemi gerçek yük altında ölçmeyi gerektirir.</p>



<p class="wp-block-paragraph">Doğru zihinsel model şudur:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Virtual Threads, bloklamayı ucuzlatır; kaynakları sınırsızlaştırmaz.</p>
</blockquote>
<p><a href="https://alikarahisar.com/blog/java-virtual-threads/">Java Virtual Threads: Daha Fazla Thread Değil, Daha Doğru Bir Eşzamanlılık Modeli</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alikarahisar.com/blog/java-virtual-threads/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Sonsuz Kaydırma Algoritmaları ve Neden Önlem Alınmalı</title>
		<link>https://alikarahisar.com/blog/sonsuz-kaydirma-algoritmalari-ve-neden-onlem-alinmali/</link>
					<comments>https://alikarahisar.com/blog/sonsuz-kaydirma-algoritmalari-ve-neden-onlem-alinmali/#respond</comments>
		
		<dc:creator><![CDATA[Ali Karahisar]]></dc:creator>
		<pubDate>Sat, 29 Aug 2026 13:34:14 +0000</pubDate>
				<category><![CDATA[Hayatın içinden]]></category>
		<guid isPermaLink="false">https://alikarahisar.com/blog/?p=32</guid>

					<description><![CDATA[<p>En büyük ve modern tuzağımız; Infinite Scrool yani &#8220;Sonsuz Kaydırma&#8220;. Belirli bir platformun bağımlısı olmamak, dijital savunma hattını kurmak için en doğru zaman. &#8220;Dipsiz kuyu&#8221; sadece bir uygulama tasarımı değil, bir dikkat ekonomisi stratejisidir. Bu yüzden bizim stratejimiz de &#8220;Bütünsel Dijital Hijyen&#8221; üzerine kurulu olmalı. İşte tüm platformlarda kontrolü elinde tutmanı sağlayacak 3 Katmanlı Savunma [&#8230;]</p>
<p><a href="https://alikarahisar.com/blog/sonsuz-kaydirma-algoritmalari-ve-neden-onlem-alinmali/">Sonsuz Kaydırma Algoritmaları ve Neden Önlem Alınmalı</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">En büyük ve modern tuzağımız; Infinite Scrool yani &#8220;<strong>Sonsuz Kaydırma</strong>&#8220;. Belirli bir platformun bağımlısı olmamak, dijital savunma hattını kurmak için en doğru zaman. &#8220;Dipsiz kuyu&#8221; sadece bir uygulama tasarımı değil, bir <strong>dikkat ekonomisi</strong> stratejisidir. Bu yüzden bizim stratejimiz de &#8220;<strong>Bütünsel Dijital Hijyen</strong>&#8221; üzerine kurulu olmalı.</p>



<span id="more-32"></span>



<p class="wp-block-paragraph">İşte tüm platformlarda kontrolü elinde tutmanı sağlayacak <strong>3 Katmanlı Savunma Stratejisi</strong>:</p>



<h2 class="wp-block-heading">1. Katman: Cihaz Seviyesinde Engeller (Fiziksel Bariyerler)</h2>



<p class="wp-block-paragraph">Algoritmalar sürtünmesiz bir deneyim ister. Bizim görevimiz, araya bilinçli &#8220;sürtünmeler&#8221; eklemek.</p>



<ul class="wp-block-list">
<li><strong>Gri Tonlama Modu (Grayscale):</strong> Instagram ve TikTok gibi platformlar renk psikolojisini (kırmızı bildirimler, canlı renkler) dopamin tetiklemek için kullanır. Ekranı siyah-beyaz yapmak, içeriğin &#8220;lezzetini&#8221; alır ve seni uygulamadan soğutur.</li>



<li><strong>Ana Ekran Temizliği:</strong> Sosyal medya uygulamalarını ana ekranından kaldır. Onları bir klasörün içine, en son sayfaya sakla. Uygulamaya ulaşmak için &#8220;arama&#8221; çubuğunu kullanmak zorunda kalman, beynine <em>&#8220;Gerçekten girmek istiyor musun?&#8221;</em> sorusunu sorması için zaman tanır.</li>



<li><strong>Bildirim Diktatörlüğüne Son:</strong> Sadece gerçek insanlardan gelen mesajlar için bildirim aç. Beğeniler, &#8220;X kişisi şunu paylaştı&#8221; gibi dürtüklemeler tamamen kapalı olmalı.</li>
</ul>



<h2 class="wp-block-heading">2. Katman: Psikolojik &#8220;Durma İpuçları&#8221; Oluşturma</h2>



<p class="wp-block-paragraph">Sonsuz kaydırmanın en büyük silahı, zaman algını yok etmesidir. Bunu şu yöntemlerle kırabilirsin:</p>



<ul class="wp-block-list">
<li><strong>&#8220;Neden Buradayım?&#8221; Testi:</strong> Bir uygulamayı açmadan önce kendine şu üç sorudan birini sor:
<ol start="1" class="wp-block-list">
<li>Belirli bir şeyi mi arıyorum?</li>



<li>Birine cevap mı vereceğim?</li>



<li>Sadece canım mı sıkıldı? (Cevap buysa, 5 dakika süre sınırı koy).</li>
</ol>
</li>



<li><strong>Fiziksel Çapalar:</strong> Sosyal medyada gezinirken ayakta durmak veya tek ayak üzerinde durmaya çalışmak gibi rahatsız edici pozisyonlar dene. Beynin rahatsızlık hissettiğinde, &#8220;kaydırma&#8221; büyüsü bozulur.</li>



<li><strong>Bölümlendirilmiş Zaman (Time Boxing):</strong> &#8220;Günün her anı bakabilirim&#8221; yerine, &#8220;Sadece akşam 19:00 &#8211; 19:30 arası bakacağım&#8221; kuralını koy.</li>
</ul>



<h2 class="wp-block-heading">3. Katman: Algoritmayı Terbiye Etmek (Aktif Direniş)</h2>



<p class="wp-block-paragraph">Algoritma senin neyi sevdiğini değil, neye <strong>uzun süre baktığını</strong> ölçer. Onu yanlış besleyerek şaşırtabilirsin:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>Eylem</strong></td><td><strong>Algoritma Üzerindeki Etkisi</strong></td></tr></thead><tbody><tr><td><strong>Keşfet Sekmesinden Kaçın</strong></td><td>Algoritmanın seni manipüle ettiği en zayıf noktanı kapatır.</td></tr><tr><td><strong>&#8220;İlgilenmiyorum&#8221; Butonu</strong></td><td>Seni içine çeken içerikleri gördüğünde hemen bu butonu kullan.</td></tr><tr><td><strong>Takip Listesini Daralt</strong></td><td>Sadece sana değer katan, gerçek kişileri takip et. Sayı azaldığında &#8220;akış&#8221; mecburen biter.</td></tr><tr><td><strong>Arama Geçmişini Temizle</strong></td><td>Algoritmanın senin hakkındaki &#8220;profilini&#8221; periyodik olarak sıfırla.</td></tr></tbody></table></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>Altın Kural:</strong> Eğer bir platformda &#8220;son&#8221; (End of Feed) yazısını görmüyorsan, o platform senin için çalışmıyordur; sen o platform için çalışıyorsundur.</p>
</blockquote>



<p class="wp-block-paragraph">Bu stratejileri bir &#8220;dijital anayasa&#8221; gibi düşünebilirsin. Her hafta birini uygulamaya koymak, birden hepsini denemekten daha kalıcı sonuçlar verir.</p>
<p><a href="https://alikarahisar.com/blog/sonsuz-kaydirma-algoritmalari-ve-neden-onlem-alinmali/">Sonsuz Kaydırma Algoritmaları ve Neden Önlem Alınmalı</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alikarahisar.com/blog/sonsuz-kaydirma-algoritmalari-ve-neden-onlem-alinmali/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Son Gelişmeler &#8211; 1</title>
		<link>https://alikarahisar.com/blog/son-gelismeler-1/</link>
					<comments>https://alikarahisar.com/blog/son-gelismeler-1/#respond</comments>
		
		<dc:creator><![CDATA[Ali Karahisar]]></dc:creator>
		<pubDate>Sun, 23 Aug 2026 18:45:35 +0000</pubDate>
				<category><![CDATA[Genel]]></category>
		<category><![CDATA[Hayatın içinden]]></category>
		<category><![CDATA[Hayatın İçinden]]></category>
		<guid isPermaLink="false">https://alikarahisar.com/blog/?p=21</guid>

					<description><![CDATA[<p>Son gelişmeler neler ve burada neler oldu? Eski yazılar nerede? Ben bunları açıklayayım da sen ister oku, istersen okuma. Öncelikle kullandığım tema benim, adı da Themini. Bir yaz akşamında yaptım onu ve yayına aldığım ilk WordPress temam kendisi. İlerleyen süreçte açık kaynak olarak da yayınlayacağım. İkinci olarak ise DB uçtu, ayrıntılarından bahsedemeyeceğim ama kanatlandı ve [&#8230;]</p>
<p><a href="https://alikarahisar.com/blog/son-gelismeler-1/">Son Gelişmeler &#8211; 1</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Son gelişmeler neler ve burada neler oldu? Eski yazılar nerede? Ben bunları açıklayayım da sen ister oku, istersen okuma.</p>



<span id="more-21"></span>



<p class="wp-block-paragraph">Öncelikle kullandığım tema benim, adı da Themini. Bir yaz akşamında yaptım onu ve yayına aldığım ilk <a href="https://tr.wordpress.org/" data-type="link" data-id="https://tr.wordpress.org/" target="_blank" rel="noopener">WordPress</a> temam kendisi. İlerleyen süreçte açık kaynak olarak da yayınlayacağım.</p>



<p class="wp-block-paragraph">İkinci olarak ise DB uçtu, ayrıntılarından bahsedemeyeceğim ama kanatlandı ve gitti. Arkasından göz yaşı da dökecek değilim. Baştan yazarız, kökü bende sonuçta. Boş bir vakitte temanın kendi sayfasını da hazırlayacağım.</p>



<h2 class="wp-block-heading">Bundan Sonra Nasıl İlerleyecek?</h2>



<p class="wp-block-paragraph">Henüz buradan söyleyemeyeceğim bir gelişme var hayatımda, onunla ilgili içerikler ve eskiden olduğu gibi teknik içerikler devam edecek ama biraz daha artık <a href="https://tr.wikipedia.org/wiki/G%C3%BCnl%C3%BCk" target="_blank" rel="noopener">günce</a> gibi kullanacağım.</p>



<p class="wp-block-paragraph">Hayatımızın her alanına işlemiş olan AI ile ilgili içerikler de yayınlamayı düşünüyorum. Eğer başarabilirsem ki söz veremiyorum ama yazıların içeriklerini aynı zamanda video olarak da yapmak istiyorum. Örneğin teknik bir konu hakkındaki paylaşımın video hali de olsun istiyorum ama bakacağız.</p>



<h2 class="wp-block-heading">Eski Yazılar Nerede?</h2>



<p class="wp-block-paragraph">Eski yazıların bir kısmı duruyor, ufak bir düzenlemeden sonra sıra ile devreye almaya çalışacağım ama ne zaman olur, inanın bilmiyorum. Çünkü bu sırada şu an bu yazıyı okuduğunuz temayı da yapıyorum.</p>



<h2 class="wp-block-heading">Reklamlar Neden Bu kadar Fazla?</h2>



<p class="wp-block-paragraph">Ya aslına bakarsanız reklam yerleşimlerini ben yapmadım. Google Adsense kendisi otomatik yerleştiriyor ve sayısını da kendi belirliyor. Müsait bir vakitte, onun da ayarları ile ilgileneceğim. Ben bile rahatsız oldum açıkcası.</p>
<p><a href="https://alikarahisar.com/blog/son-gelismeler-1/">Son Gelişmeler &#8211; 1</a> yazısı ilk önce <a href="https://alikarahisar.com/blog">Ali Karahisar</a> üzerinde ortaya çıktı.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alikarahisar.com/blog/son-gelismeler-1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
