Java Loom'da Pinning: Sanal Thread'ler Neden Tıkanır
Sanal thread pinning, carrier thread'leri kilitleyip performansı thread pool seviyesine düşürür; JDK 24'teki JEP 491 bu sorunu büyük ölçüde çözüyor.
Java'nın sanal iş parçacıkları (virtual threads), bloke olduklarında normalde carrier thread'i serbest bırakıp binlerce eşzamanlı işlemi az sayıda OS thread'iyle yönetmeyi mümkün kılar. Ancak 'pinning' adı verilen durumda bu ayrılma gerçekleşmez: sanal thread bloke olduğu halde carrier'a yapışık kalır ve o carrier tamamen işlevsiz hale gelir. Sistemdeki tüm carrier'lar aynı anda pinlenirse, uygulama dıştan bakıldığında deadlock gibi görünen bir tıkanmaya girer; gerçek bir vaka bunu saniyede sabit 420 istekte donan bir servis üzerinden gösteriyor.
Pinning'in yalnızca iki nedeni var: JDK 21-23'te synchronized blok içinde bloke olmak (kendi kodunuzda ya da ConcurrentHashMap.computeIfAbsent gibi kütüphane içi kilitlerde) ve native çerçeveler (JNI, Foreign Function & Memory çağrıları, hatta static initializer'lar). Sıradan I/O, BlockingQueue, ReentrantLock ve CompletableFuture gibi standart bloklama mekanizmaları Loom için yeniden yazıldığından pinlenmez.
JDK 24 ile gelen JEP 491, monitor sahipliğini carrier yerine sanal thread'e bağlayarak synchronized kaynaklı pinlemeyi kod değiştirmeden ortadan kaldırıyor; geriye yalnızca native çerçeve kaynaklı pinlemeler kalıyor. Eski tanı bayrağı -Djdk.tracePinnedThreads JDK 24'te kaldırıldı; bunun yerine jdk.VirtualThreadPinned JFR olayı ve jcmd Thread.dump_to_file komutu üretimde izlemeye devam edilmesi gereken araçlar. Çözüm tarafında ise synchronized yerine ReentrantLock kullanmak, kilidi dış çağrı boyunca tutmamak ve native bloklayan çağrıları ayrı bir platform thread havuzuna izole etmek öne çıkıyor.