Java backend geliştirme ekiplerinde virtual thread kullanımı, JDBC havuzu veya pinning yüzünden beklenen kapasiteyi vermeyebilir. JFR, HikariCP metrikleri ve sabit yük testiyle sorunu ayırmayı inceleyin.
Spring Boot'ta Java Microservices için Virtual Thread Pinning Analizi
Java backend geliştirme ve Java microservices için doğru hipotez
Virtual thread'e geçişte ilk hipotez 'daha çok eşzamanlı istek, daha yüksek throughput' olmamalıdır. Java microservices servisinde throughput'u çoğu zaman PostgreSQL bağlantı sayısı, uzak HTTP bağımlılığının eşzamanlılık limiti veya CPU belirler. Virtual thread, bekleyen Java işlerini az sayıda carrier thread üzerinde park edebildiği için platform thread başına bellek maliyetini düşürür; ancak 40 bağlantılı bir HikariCP havuzundan aynı anda en fazla 40 transaction çalışabilir. Bu nedenle önce şu üç değeri aynı zaman penceresinde toplayın: HTTP p99, `hikaricp.connections.pending` ve veritabanı tarafındaki aktif bağlantı sayısı. Prometheus kullanıyorsanız `/actuator/prometheus` çıktısında Hikari metriklerini doğrulayın; metrik adı sürüme göre etikete dönüşebileceğinden önce endpoint'te gerçek adı arayın:
curl -s http://localhost:8080/actuator/prometheus | grep -E 'hikaricp_.*(pending|active|timeout)'Bu ayrım, java eğitimi veya java programlama eğitimi materyallerinde sık atlanan bir kapasite kuralıdır: thread sayısı kuyrukları yok etmez, yalnızca hangi kaynağın kuyruk oluşturduğunu değiştirir. Örneğin 200 eşzamanlı istekte `connections.pending` yükselirken CPU %35 ise uygulama thread-kısıtlı değildir, DB pool-kısıtlıdır. Buna karşılık pending sıfırken CPU %90'a yaklaşıyor ve JFR'da yoğun JSON serileştirme görünüyorsa daha çok virtual thread, işlemci tabanlı darboğazı çözmez. Bu ayrımı yapmak için PostgreSQL'de uygulama kullanıcısının oturumlarını her 5 saniyede bir kaydedin:
watch -n 5 "psql '$DATABASE_URL' -c \"select state, count(*) from pg_stat_activity where usename = 'app' group by state;\""Spring Framework içinde virtual thread yürütücüsünü sınırlarıyla kurmak
Spring Framework tabanlı MVC uygulamasında, ilgili Spring Boot yapılandırmasıyla virtual thread desteğini etkinleştirmek HTTP istek işleyicilerinin yürütülmesini etkileyebilir; fakat `@Async` metodları veya elle oluşturulmuş executor'lar otomatik olarak aynı politikayı izlemez. Bunları açıkça tanımlayın ve executor bean'ini isimle bağlayın. Aşağıdaki örnekte `SimpleAsyncTaskExecutor` her görev için virtual thread üretir; buna DB yazma işini doğrudan bırakmak yerine çağrının admission control katmanından geçmesini sağlayın.
@Configuration
@EnableAsync
class AsyncConfig {
@Bean("ioVirtualExecutor")
AsyncTaskExecutor ioVirtualExecutor() {
var executor = new SimpleAsyncTaskExecutor("io-vt-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(300);
return executor;
}
}
@Service
class ExportService {
@Async("ioVirtualExecutor")
CompletableFuture<Export> create(UUID id) {
return CompletableFuture.completedFuture(runExport(id));
}
}Buradaki `concurrencyLimit`, HikariCP boyutunun yerine geçmez. Örneğin pool 40 ise 300 görev aynı anda DB'ye yönelip uzun bir bağlantı edinme kuyruğu yaratabilir. Sınırı endpoint, tenant veya iş türüne göre ayrıca tasarlayın.Yaygın hata `ThreadLocal` tabanlı request context'in virtual thread ile kaybolacağını sanmak ya da tersine async sınıra taşınacağını varsaymaktır. Bir virtual thread kendi `ThreadLocal` değerini taşır, ancak `@Async` ile yeni göreve geçildiğinde request thread'inin MDC değeri otomatik taşınmaz. Log korelasyonu gerekiyorsa Micrometer context propagation kullanın ve yalnızca ihtiyaç duyulan accessor'ları kaydedin. Büyük nesneleri `ThreadLocal` içine koymak her virtual thread için çoğalacağından kaçının. Uygulamada davranışı test etmek için MDC'nin child görevde gerçekten mevcut olup olmadığını ölçen bir test ekleyin:
@Test
void mdcIsNotImplicitlyPropagatedToAsyncWork() throws Exception {
MDC.put("traceId", "t-42");
var seen = executor.submit(() -> MDC.get("traceId")).get();
assertThat(seen).isNull();
}JFR ile virtual thread pinning kanıtı ve once-sonra karşılaştırması
Pinning teşhisini thread dump'taki çok sayıda virtual thread ile yapmayın. Pinning, bir virtual thread park edemediği halde carrier thread'i bloke tuttuğunda oluşur; etkisini ancak hedef JVM'nizde Java Flight Recorder ile ölçebilirsiniz. Yük testi başlamadan önce iki dakikalık profil kaydı başlatın, ardından Java Mission Control'de `jdk.VirtualThreadPinned` olaylarını toplam süre, stack trace ve carrier thread açısından gruplayın:
jcmd $PID JFR.start name=vt-baseline settings=profile duration=120s filename=/tmp/vt-baseline.jfr
jcmd $PID JFR.checkJFR'da pinning olayı yok ama latency yüksekse problem büyük olasılıkla pinning değildir; connection acquisition, uzak servis gecikmesi veya CPU profili üzerinde çalışın.Pinning stack trace'i native çağrı veya uzun süren bir `synchronized` kritik bölgesini gösterirse kilidin kapsamını küçültün. Aşağıdaki anti-pattern'de native yazma sırasında monitor tutulur. Bu, bazı çalışma zamanı ve çağrı yollarında carrier'ın bloklanmasına neden olabilir; kesin karar JFR kaydıyla verilmelidir. Refaktörde sadece paylaşılan durum monitor altında snapshot edilir, potansiyel bloklayan çağrı monitor dışına çıkarılır.
// Kötü: nativeSend yavaşlarsa monitor boyunca tutulur
synchronized void publish(byte[] body) {
nativeSend(clientHandle, body);
}
// Daha dar kritik bölüm
void publish(byte[] body) {
final long handle;
synchronized (this) {
handle = clientHandle;
}
nativeSend(handle, body);
}Bu değişiklik thread-safety'yi otomatik garanti etmez. `clientHandle` kapanabiliyorsa reference count, read-write lock veya lifecycle state machine gerekir. Refaktörden sonra aynı istek hızı, aynı veri seti ve aynı JVM bayraklarıyla `/tmp/vt-after.jfr` kaydını alın; karşılaştırmada pinning toplam süresi, p99 ve hata oranı birlikte düşmüyorsa değişikliği başarı saymayın.Hibernate ORM ve Spring Data JPA altında bağlantı havuzu darboğazı
hibernate orm ve spring data jpa kullanan bir servis virtual thread ile daha fazla bekleyen transaction taşıyabilir, fakat bu durum veritabanına daha fazla paralel sorgu gönderilmesi gerektiği anlamına gelmez. Önce HikariCP'nin connection timeout değerini uygulamanın HTTP deadline'ından kısa seçin. Böylece 30 saniyelik istemci deadline'ı varken 30 saniye pool bekleyip anlamsız iş yapan istekler yerine, kontrollü bir hata ve gözlemlenebilir timeout üretirsiniz. Örneğin 2 saniyelik connection edinme bütçesi için:
spring:
datasource:
hikari:
maximum-pool-size: 40
minimum-idle: 10
connection-timeout: 2000
validation-timeout: 1000
jpa:
open-in-view: false`open-in-view: false`, transaction dışına sarkan lazy loading nedeniyle bağlantının request boyunca tutulması riskini azaltır. Ancak controller DTO'su lazy ilişkiye erişiyorsa bunu kapatmak `LazyInitializationException` üretebilir; çözüm OSIV'i yeniden açmak değil, repository sorgusunu projection veya kontrollü fetch planıyla tasarlamaktır.Pool boyutunu CPU çekirdek sayısına göre ezbere belirlemeyin. 40 bağlantının doğru olup olmadığını DB'nin `max_connections` değerini, aynı cluster'daki diğer servisleri ve sorgu süresini birlikte ölçerek bulun. Sabit arrival rate ile iki deney yapın: platform thread yürütücüsü ve virtual thread yürütücüsü. Her deneyde aynı 120 saniye boyunca p95, p99, `connections.pending`, `connections.timeout` ve PostgreSQL `pg_stat_statements.mean_exec_time` değerlerini kaydedin. `wrk2` ile örnek yük komutu şöyledir:
wrk -t4 -c200 -d120s -R800 -s scripts/create-order.lua http://localhost:8080/api/ordersVirtual thread deneyinde p99 iyileşirken `pending` ve DB execution time yükseliyorsa uygulama daha fazla işi DB kuyruğuna taşımıştır. Bu sonuçta pool'u körlemesine büyütmek yerine pahalı sorgunun indeksini `EXPLAIN (ANALYZE, BUFFERS)` ile inceleyin.Spring AI, Spring MCP ve Model Context Protocol araç çağrılarında kota
spring ai ile bir model yanıtı üretip spring mcp üzerinden model context protocol araçları sunan Java servislerinde virtual thread, çok sayıda yavaş araç çağrısının platform thread tüketmesini azaltabilir. Fakat bir araç çağrısı Spring Data JPA üzerinden DB'ye gidiyorsa sınırsız eşzamanlılık, HikariCP kuyruğunu model isteği sayısıyla büyütür. Tool handler önüne `Semaphore` ile açık bir kota koyun ve kota alınamadığında çağrıyı hızlıca reddedin veya sıraya alın. Bu yaklaşım thread sayısına değil, gerçekten kıt olan downstream kapasitesine göre admission control uygular.
@Component
class CustomerLookupTool {
private final Semaphore permits = new Semaphore(24);
CustomerView lookup(UUID customerId) {
if (!permits.tryAcquire()) {
throw new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS,
"customer lookup capacity exhausted");
}
try {
return repository.findViewById(customerId)
.orElseThrow(NoSuchElementException::new);
} finally {
permits.release();
}
}
}24 değeri örnektir; bunu DB pool'u, diğer endpoint'lerin rezervi ve gerçek sorgu gecikmesiyle belirleyin. Ayrıca modelin paralel tool call sayısına güvenmeyin; gateway seviyesinde tenant başına rate limit ekleyin.Bu konu bir spring boot eğitimi, java kursu veya java fullstack eğitimi içinde yalnızca `spring.threads.virtual.enabled` ayarı olarak anlatılmamalıdır. Üretimde gerekli minimum doğrulama matrisi şudur: normal HTTP endpoint'i, `@Async` işi, JDBC transaction'ı, yavaş uzak HTTP çağrısı ve MCP tool çağrısı için ayrı yük senaryosu çalıştırın. Her senaryoda JFR eventleri, HikariCP pending metriği ve istemci tarafı p99 aynı dashboard'a yazılmalıdır. Böylece bir model aracı altında görülen gecikmenin carrier pinning mi, DB kotası mı, yoksa uzak sağlayıcının timeout'u mu olduğu ölçülebilir biçimde ayrılır.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Java Spring Boot ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
java backend geliştirme projesinde virtual thread pinning nasıl bulunur?
Önce sabit yük altında `jcmd $PID JFR.start settings=profile duration=120s filename=/tmp/app.jfr` çalıştırın. Java Mission Control'de `jdk.VirtualThreadPinned` olaylarını stack trace'e göre gruplayın. Aynı zaman aralığında HTTP p99 ve HikariCP pending metriğini kaydedin. Pinning olayı yoksa, sadece thread dump'a bakarak monitor refaktörü yapmayın.
spring data jpa ve hibernate orm ile virtual thread kullanırken HikariCP pool kaç olmalı?
Sabit bir sayı yoktur. Önce mevcut pool boyutunda `hikaricp.connections.pending`, timeout sayısı ve PostgreSQL `pg_stat_statements` sorgu sürelerini ölçün. Pool artırıldığında pending azalırken DB CPU, lock wait veya sorgu p99 yükseliyorsa sınır uygulama değil veritabanıdır. HTTP deadline'dan kısa bir `connection-timeout` belirleyip endpoint bazlı eşzamanlılık kotası ekleyin.
spring ai ve spring mcp ile model context protocol tool çağrılarında virtual thread yeterli mi?
Hayır. Virtual thread bekleyen işlerin thread maliyetini düşürür, ancak tool'un DB bağlantısı, uzak API kotası veya model sağlayıcı limiti sınırlayıcı olmaya devam eder. Her tool için `Semaphore` veya gateway rate limit uygulayın; permit bekleme süresi, HikariCP pending ve tool p99 değerlerini ayrı metrik olarak yayınlayın.
java programlama eğitimi veya java kursu kapsamında virtual thread için hangi test yapılmalı?
Aynı endpoint ve aynı veri setiyle platform thread ve virtual thread olmak üzere iki çalıştırma yapın. `wrk -R` ile arrival rate'i sabitleyin, her koşulda JFR kaydı alın ve p50-p99, hata oranı, GC pause, HikariCP pending değerlerini karşılaştırın. Sadece ortalama response time ile karar vermek tail latency ve pool kuyruğunu gizler.
AI / LLM Discovery
Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


