چه چیزی در JDK 24 تغییر کرده است

JDK 24 با اجرای JEP 491 مکانیزم پین‌کردن حامل مرتبط با مانیتور را حذف کرد. این مکانیزم همان عاملی بود که در JDK 21 باعث می‌شد استفاده از تردهای مجازی در کدهای دارای synchronized پرریسک باشد. حذف این پین، مسیر مهاجرت به تردهای مجازی را برای سرویس‌های مبتنی بر I/O مسدودکننده ساده‌تر می‌کند، اما هنوز پین‌هایی در حوالی فریم‌های native، بارگذاری کلاس و عملیات فایل محلی در لینوکس وجود دارد. بنابراین هنوز باید رویداد jdk.VirtualThreadPinned در JDK Flight Recorder (JFR) را رصد کنید.

ریسک‌های عملیاتی پس از برداشته شدن سقف تردها

با حذف محدودیت تعداد تردهای سنتی، مرزهای همزمانی از سطح JVM و حامل‌ها به منابع پایین‌دستی منتقل شده‌اند. اکنون مشکل اصلی «گرسنگی ترد حامل» نیست؛ استخرهای اتصال، نرخ‌دهی (rate limits)، تعداد توصیف‌کننده‌های فایل و سرویس‌های خارجی مرزهای واقعی همزمانی به حساب می‌آیند. افزایش تعداد تردها بدون بهینه‌سازی این منابع خارجی تنها به اتمام منابع خارجی و نه افزایش کارایی منجر می‌شود.

نمونه‌های عملی از تبدیل شدن منابع پایین‌دست به گلوگاه

  • افزایش همزمانی بدون افزایش اندازه استخر JDBC باعث تشکیل صف در لایه اتصال و افزایش latency می‌شود.
  • محدودیت‌های سرویس‌های پایین‌دست، از جمله rate-limit یا سقف همزمانی پایگاه داده، سریعاً به گلوگاه بدل می‌شوند.

مسألهٔ خاموش ThreadLocal

در بنچمارک‌های کنترل‌شده یکی از مشکلات آشکار، از دست رفتن کش‌هایی بود که به ThreadLocal متکی بودند. تردهای مجازی عمر کوتاه‌تری دارند و فرض بازاستفادهٔ cacheهای مبتنی بر ThreadLocal غالباً نقض می‌شود؛ در نتیجه مقداردهی مجدد یا نبود کش به‌صورت خاموش و بدون هشدار اتفاق می‌افتد.

مثلاً در بنچمارک همراه این مطلب، یک ThreadLocal.withInitial() برای یک بار کار مشخص تحت تردهای پلتفرم 200 بار مقداردهی اولیه شد، اما همان بار کاری تحت تردهای مجازی 443,267 بار مقداردهی اولیه شد — نسبت 2,216× — که ابتدا به‌صورت فشار GC غیرمنتظره نمایان شد.

Scoped Values: جایگزینی مناسب برای کانتکس‌های درخواست

Scoped Values (نهایی‌شده در JDK 25) مکانیزمی مناسب برای انتقال امن کانتکس درخواست بین تردها و وظایف هستند. آنها مشکلات InheritableThreadLocal را ندارند و به‌طور خودکار به وظایف فرزند در StructuredTaskScope منتقل می‌شوند. توجه کنید که API نهایی‌شده شامل یک تغییر است: استفاده از ScopedValue.orElse(null) دیگر مناسب نیست؛ باید مقدار پیش‌فرض غیر null یا مکانیزم دیگری برای مقداردهی در نظر گرفته شود.

چه زمانی Spring MVC کفایت می‌کند و چه زمانی WebFlux لازم است

تردهای مجازی کار با سرویس‌های درخواست-پاسخ مبتنی بر I/O مسدودکننده را ساده و قابل‌مدیریت می‌کنند، بنابراین در بسیاری از سناریوها Spring MVC همراه با تردهای مجازی یک گزینهٔ مناسب است. با این حال برای بارهایی که مدیریت backpressure و جریان داده اهمیت دارد — مانند streaming، SSE یا WebSocket — معماری‌های reactive و چارچوب‌هایی مانند Spring WebFlux انتخاب بهتری هستند.

نکات عملی برای مهاجرت به محیط تولید

  • نظارت و آلارم‌دهی: رویداد jdk.VirtualThreadPinned در JFR را فعال کنید و آستانه‌های هشدار برای نرخ پین‌ها تعریف کنید.
  • بازبینی ThreadLocalها: همهٔ ThreadLocalها را شناسایی و بررسی کنید؛ برای کش‌ها یا داده‌های کانتکست، از Scoped Value یا مکانیزم‌هایی با عمر مشخص استفاده کنید.
  • اندازه‌گذاری منابع پایین‌دست: پیش از افزایش همزمانی، سقف استخرهای اتصال، محدودیت‌های سرویس‌های پایین‌دست و تعداد توصیف‌کننده‌های فایل را بازبینی و در صورت نیاز افزایش دهید یا نرخ‌دهی اعمال کنید.
  • آزمایش تحت بار واقعی: تست‌های بار با پیکربندی JVM و استخرهای مختلف اجرا کنید تا گلوگاه‌های جدید شناسایی شوند.
  • بازنویسی کدهای مبتنی بر فرض بازاستفادهٔ ترد: کدهایی که به reuse ترد متکی‌اند (مثل کش‌های پنهان در ThreadLocal) را بازنویسی یا مستندسازی کنید.

نتایج بنچمارک و برداشت از داشبوردها

در یک سناریوی کنترل‌شده روی نمونه c7i.2xlarge (8 vCPU، 15 گیگابایت RAM) با اوبونتو 24.04 و Temurin 25.0.3، برای یک endpoint سبک I/O با 500 کاربر همزمان در بازهٔ 60 ثانیه نتایج زیر ثبت شد:

  • Throughput: از 3,594 req/s با تردهای پلتفرم به 7,426 req/s با تردهای مجازی (+107%).
  • p99 latency: از 147 ms به 53 ms (−64%).
  • نرخ خطا: صفر در هر دو حالت برای این مسیر خاص.

این اعداد نشان‌دهندهٔ پتانسیل تردهای مجازی در بارهای I/O-bound هستند، اما فشار واقعی غالباً به منابع خارج از JVM منتقل می‌شود و باید آن منابع را مدیریت کرد.

توپولوژی بنچمارک دو اپلیکیشن با تنظیمات یکسان و تفاوت مدل تردینگ تله‌متری Throughput و p99 latency در اجرای پلتفرم و مجازی

چک‌لیست مهاجرت سریع

  1. رویدادهای JFR مرتبط را فعال و داشبورد مناسب بسازید (به‌ویژه jdk.VirtualThreadPinned).
  2. تمام ThreadLocalها را شناسایی کنید؛ برای کش‌ها از Scoped Value یا کش‌های صریح با عمر درخواست استفاده کنید.
  3. سقف استخرهای اتصال و محدودیت‌های پایین‌دست را بررسی و متناسب تنظیم کنید تا از فروپاشی سرویس جلوگیری شود.
  4. تست‌های بار واقعی را با پیکربندی‌های مختلف استخر تکرار کنید و الگوهای شکست را ثبت کنید.
  5. در سیستم‌های حساس به backpressure از معماری reactive و Spring WebFlux استفاده کنید.

جمع‌بندی و چشم‌انداز

تردهای مجازی تغییر بنیادینی در مدل همزمانی جاوا ایجاد کرده‌اند و مدیریت منابع را ساده‌تر می‌کنند، اما مرزهای همزمانی را از داخل JVM به بیرون منتقل کرده‌اند. موفقیت در محیط تولید مستلزم هم‌زمانی بین پیکربندی JVM، بازبینی کد و طراحی سیستم است. اتخاذ الگوهای جدید مانند Scoped Values و تقویت نظارت روی منابع پایین‌دست مسیر مهاجرت را امن‌تر می‌کند.

برای مراجعهٔ فنی بیشتر JEPهای مرتبط را در OpenJDK بررسی کنید و مستندات Spring را در spring.io مطالعه کنید.