عنوان:

‫جلوگیری از نشت حافظه ناشی از سوء استفاده از APIهای Microsoft.Extensions.Configuration در NET.


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۰۱ ۰۹:۳۳
آدرس: www.dntips.ir
نشت حافظه یکی از چالش‌برانگیزترین مسائل در توسعه نرم‌افزار است که می‌تواند منجر به کاهش عملکرد و حتی از کار افتادن برنامه (OutOfMemoryException) شود. این مقاله به بررسی یکی از دلایل رایج نشت حافظه در برنامه‌های .NET، به‌ویژه در سیستم‌عامل ویندوز، می‌پردازد که ناشی از استفاده نادرست از APIهای Microsoft.Extensions.Configuration است. این مشکل می‌تواند هم در برنامه‌های جدید و هم در برنامه‌هایی که از .NET 6 یا پایین‌تر به .NET 7 یا بالاتر به‌روزرسانی شده‌اند، بروز پیدا کند. درک مکانیسم این نشت و نحوه شناسایی و رفع آن برای توسعه‌دهندگان .NET حیاتی است.

تفاوت‌های .NET 6 و .NET 7+ در مدیریت حافظه
یکی از دلایلی که این نشت حافظه در .NET 7 و نسخه‌های بالاتر مشهودتر است، به تفاوت در نحوه مدیریت بلوک‌های حافظه برای هیپ‌های جمع‌آوری زباله (GC Heaps) بازمی‌گردد. در .NET 6 و نسخه‌های قدیمی‌تر (و .NET Framework)، از سگمنت‌های (Segments) بزرگ‌تر و مختص هر هیپ استفاده می‌شد. اما در .NET 7 و نسخه‌های جدیدتر، از مناطق (Regions) کوچک‌تر و قابل استفاده مجدد استفاده می‌شود. این تغییر در مدیریت حافظه، گرچه بهینه‌سازی‌هایی را به همراه داشته است، اما می‌تواند باعث شود نشت‌های حافظه موجود، با سرعت بیشتری خود را نشان دهند و منجر به پر شدن حافظه با مناطق غیرقابل استفاده شوند. برای اطلاعات بیشتر در مورد تفاوت بین سگمنت‌ها و مناطق، می‌توانید به مقاله Maoni Stephens، معمار GC در .NET، مراجعه کنید. طبق یافته‌ها، این نوع نشت حافظه بیشتر در محیط ویندوز مشاهده شده است.

نشانه‌های شناسایی نشت حافظه
اگر برنامه شما تحت تأثیر این مشکل قرار گرفته باشد، ممکن است یک یا چند مورد از علائم زیر را مشاهده کنید:
  • افزایش تدریجی مصرف حافظه: حتی در شرایط ترافیک ثابت یا کم، مصرف حافظه برنامه به مرور زمان افزایش می‌یابد.
  • خطای OutOfMemoryException: در صورت تداوم رشد حافظه برای مدت طولانی، ممکن است برنامه با خطای OutOfMemoryException مواجه شده و دچار افت عملکرد شود.
  • آمار GC نامعمول: مشاهده حجم زیادی از حافظه آزاد در نسل دوم (Gen2) که هرگز کاهش نمی‌یابد، از نشانه‌های این مشکل است.
  • دامپ حافظه (Memory Dumps) حاوی اشیاء خاص: در تحلیل دامپ‌های حافظه، تعداد زیادی از اشیاء System.Byte[] پین‌شده (Pinned) و System.IO.FileSystemWatcher+AsyncReadState مشاهده می‌شود.

شناسایی زودهنگام این علائم از طریق ابزارهای مانیتورینگ و تحلیل دوره‌ای دامپ‌های حافظه می‌تواند از تأثیرگذاری بر کاربران جلوگیری کند.

علت اصلی نشت حافظه
علت اصلی این نشت حافظه به نحوه استفاده و مدیریت رویدادهای تغییر فایل (File Change Events) در Microsoft.Extensions.Configuration باز می‌گردد. برخی از ارائه‌دهندگان پیکربندی (Configuration Providers) مانند FileConfigurationProvider که از فایل‌ها برای بارگذاری تنظیمات استفاده می‌کنند، برای تشخیص تغییرات در فایل‌های پیکربندی، از FileSystemWatcher استفاده می‌کنند. این FileSystemWatcher یک شیء AsyncReadState را ایجاد می‌کند که شامل یک byte[] پین‌شده است. اگر نمونه‌های IConfiguration یا IConfigurationRoot به درستی رها (Disposed) نشوند، FileSystemWatcher نیز رها نمی‌شود و در نتیجه AsyncReadState و byte[] پین‌شده آن در حافظه باقی می‌مانند. این امر منجر به تجمع تدریجی اشیاء byte[] پین‌شده و بلوک‌های حافظه غیرقابل استفاده در Gen2 می‌شود. این وضعیت در سناریوهایی که برنامه به طور مکرر نمونه‌های جدیدی از IConfiguration را ایجاد و بارگذاری می‌کند (مانند خواندن تنظیمات برای هر درخواست HTTP بدون استفاده از تزریق وابستگی یا مدیریت طول عمر مناسب)، تشدید می‌شود.

راه حل‌ها و بهترین شیوه‌ها
برای جلوگیری از این نشت حافظه، رعایت بهترین شیوه‌ها در مدیریت پیکربندی و منابع سیستم حیاتی است:
  • استفاده از تزریق وابستگی (Dependency Injection) برای IConfiguration: در ASP.NET Core و برنامه‌های .NET، IConfiguration باید به عنوان یک سرویس تک‌زی (Singleton) یا محدود به طول عمر درخواست (Scoped) تزریق شود و از ایجاد نمونه‌های جدید آن در هر بار استفاده خودداری شود. این اطمینان حاصل می‌کند که یک نمونه واحد از IConfiguration در طول عمر برنامه یا درخواست مورد استفاده قرار می‌گیرد و از ایجاد مکرر FileSystemWatcher جلوگیری می‌شود.
  • پرهیز از ایجاد نمونه‌های IConfiguration به صورت دستی و مکرر: از کدنویسی‌هایی که به طور مکرر و دستی IConfiguration یا ConfigurationBuilder را برای خواندن تنظیمات ایجاد می‌کنند، خودداری کنید. به جای آن، از نمونه‌های موجود که از طریق تزریق وابستگی در دسترس هستند، استفاده کنید.
  • مدیریت طول عمر FileSystemWatcher: اگر به طور مستقیم از FileSystemWatcher استفاده می‌کنید، مطمئن شوید که پس از اتمام نیاز به آن، شیء را به درستی Dispose می‌کنید. این کار می‌تواند با استفاده از الگوی using یا فراخوانی صریح متد Dispose() انجام شود. FileSystemWatcher یک منبع سیستم عامل است و باید به درستی آزاد شود تا از نشت منابع و حافظه جلوگیری شود.
  • بررسی و به‌روزرسانی کتابخانه‌های شخص ثالث: اطمینان حاصل کنید که هر کتابخانه شخص ثالثی که استفاده می‌کنید و به نوعی با پیکربندی یا فایل‌ها سروکار دارد، به درستی منابع خود را مدیریت می‌کند. گاهی اوقات، نشت حافظه می‌تواند از طریق وابستگی‌های (dependencies) داخلی به برنامه‌های شما نفوذ کند.
  • مانیتورینگ مداوم و تحلیل دامپ‌ها: مانیتورینگ منظم مصرف حافظه و تحلیل دوره‌ای دامپ‌های حافظه، به‌ویژه در محیط‌های تولیدی، برای شناسایی زودهنگام و رفع مشکلات حافظه حیاتی است. ابزارهایی مانند PerfView، dotnet-dump و WinDbg می‌توانند در این زمینه کمک‌کننده باشند.

نتیجه‌گیری
نشت حافظه ناشی از سوء استفاده از APIهای Microsoft.Extensions.Configuration، به‌ویژه در نسخه‌های جدیدتر .NET در ویندوز، یک مشکل جدی است که می‌تواند بر پایداری و عملکرد برنامه تأثیر منفی بگذارد. این مشکل با تجمع اشیاء System.Byte[] پین‌شده و غیرقابل استفاده در Gen2 هیپ GC مشخص می‌شود که اغلب توسط نمونه‌های FileSystemWatcher که به درستی رها (Disposed) نشده‌اند، نگهداری می‌شوند. با رعایت بهترین شیوه‌ها در مدیریت پیکربندی، استفاده صحیح از تزریق وابستگی، و مدیریت دقیق منابع سیستم مانند FileSystemWatcher، توسعه‌دهندگان می‌توانند از بروز این نشت حافظه جلوگیری کرده و برنامه‌های .NET با کارایی و پایداری بالا را توسعه دهند. مانیتورینگ و تحلیل مداوم حافظه نیز ابزارهای ضروری برای شناسایی و رفع زودهنگام این گونه مسائل هستند.


مشاهده مطلب اصلی