پشته شیء بزرگ در سیستم های ویندوز

ساخت وبلاگ

جمع کننده زباله های NET (GC) اشیاء را به اشیاء کوچک و بزرگ تقسیم می کند. هنگامی که یک جسم بزرگ است ، برخی از ویژگی های آن از نظر کوچکتر بودن شیء مهمتر می شوند. به عنوان مثال ، فشرده سازی آن - یعنی کپی کردن آن در حافظه در جای دیگر پشته - می تواند گران باشد. به همین دلیل ، جمع کننده زباله اشیاء بزرگی را بر روی قسمت بزرگ جسم (LOH) قرار می دهد. در این مقاله بحث شده است که چه چیزی یک شیء را به عنوان یک شیء بزرگ ، چگونگی جمع آوری اشیاء بزرگ و چه نوع پیامدهای عملکردی اشیاء بزرگ تحمیل می کند.

در این مقاله به بحث در مورد پشته بزرگ شیء در چارچوب . NET و هسته اصلی. این پوشش LOH را در اجرای . NET در سیستم عامل های دیگر پوشش نمی دهد.

چگونه یک شیء به LOH ختم می شود

اگر یک جسم از اندازه 85000 بایت بیشتر یا مساوی باشد ، یک شیء بزرگ محسوب می شود. این تعداد با تنظیم عملکرد تعیین شد. هنگامی که یک درخواست تخصیص شیء برای 85000 یا بیشتر بایت است ، زمان اجرا آن را در پشته شیء بزرگ اختصاص می دهد.

برای درک این معنی ، بررسی برخی از اصول مربوط به جمع آوری زباله مفید است.

جمع کننده زباله یک جمع کننده نسلی است. این سه نسل دارد: نسل 0 ، نسل 1 و نسل 2. دلیل داشتن سه نسل این است که در یک برنامه خوب تنظیم شده ، بیشتر اشیاء در Gen0 می میرند. به عنوان مثال ، در یک برنامه سرور ، تخصیص مرتبط با هر درخواست باید پس از پایان درخواست از بین برود. درخواست های تخصیص پرواز باعث می شود آن را به Gen1 تبدیل کرده و در آنجا بمیرد. در اصل ، Gen1 به عنوان یک بافر بین مناطق شیء جوان و مناطق شیء طولانی مدت عمل می کند.

اشیاء تازه تخصیص یافته نسل جدیدی از اشیاء را تشکیل می دهند و به طور ضمنی مجموعه های نسل 0 هستند. با این حال ، اگر آنها اشیاء بزرگی باشند ، آنها به پشته بزرگ (LOH) می روند ، که گاهی اوقات به عنوان نسل 3 گفته می شود. نسل 3 یک نسل فیزیکی است که به طور منطقی به عنوان بخشی از نسل 2 جمع آوری می شود.

اشیاء بزرگ متعلق به نسل 2 هستند زیرا آنها فقط در یک مجموعه نسل 2 جمع آوری می شوند. هنگامی که یک نسل جمع آوری می شود ، تمام نسل (های) جوان آن نیز جمع آوری می شوند. به عنوان مثال ، هنگامی که یک نسل 1 GC اتفاق می افتد ، هر دو نسل 1 و 0 جمع آوری می شوند. و هنگامی که یک Generation 2 GC اتفاق می افتد ، کل پشته جمع می شود. به همین دلیل ، یک Genera 2 GC نیز به عنوان GC کامل نامیده می شود. این مقاله به جای GC کامل به نسل 2 GC اشاره دارد ، اما اصطلاحات قابل تعویض هستند.

نسل ها نمای منطقی از پشته GC را ارائه می دهند. از نظر جسمی ، اشیاء در بخش های پشته مدیریت شده زندگی می کنند. یک بخش پشته مدیریت شده بخشی از حافظه است که GC با فراخوانی عملکرد VirtualAlloc به نمایندگی از کد مدیریت شده از سیستم عامل ذخیره می کند. هنگامی که CLR بارگیری می شود ، GC دو بخش پشته اولیه را اختصاص می دهد: یکی برای اشیاء کوچک (پشته شیء کوچک یا SOH) و دیگری برای اشیاء بزرگ (پشته شیء بزرگ).

درخواست های تخصیص با قرار دادن اشیاء مدیریت شده در این بخش های پشته مدیریت شده راضی می شوند. اگر شیء کمتر از 85000 بایت باشد ، برای SOH روی بخش قرار می گیرد. در غیر این صورت ، آن را در یک بخش LOH قرار داده است. بخش ها (در بخش های کوچکتر) مرتکب می شوند زیرا بیشتر و بیشتر اشیاء به آنها اختصاص می یابد. برای SOH ، اشیاء که از GC زنده مانده اند به نسل بعدی ارتقا می یابند. اشیاء که از یک مجموعه نسل 0 جان سالم به در می برند ، اکنون اشیاء نسل 1 و غیره در نظر گرفته می شوند. با این حال ، اشیاء که از قدیمی ترین نسل جان سالم به در می برند ، هنوز هم در قدیمی ترین نسل در نظر گرفته می شوند. به عبارت دیگر ، بازماندگان از نسل 2 اشیاء نسل 2 هستند. و بازماندگان از LOH اشیاء LOH (که با Gen2 جمع آوری می شوند) هستند.

کد کاربر فقط می تواند در نسل 0 (اشیاء کوچک) یا LOH (اشیاء بزرگ) اختصاص دهد. فقط GC می تواند اشیاء را در نسل 1 (با ترویج بازماندگان از نسل 0) و نسل 2 (با ترویج بازماندگان از نسل 1) "اختصاص دهد".

هنگامی که یک مجموعه زباله ایجاد می شود ، GC از طریق اشیاء زنده ردیابی می کند و آنها را جمع می کند. اما از آنجا که تراکم گران است ، GC LOH را جابجا می کند. این یک لیست رایگان از اشیاء مرده ایجاد می کند که بعداً می توانند مورد استفاده مجدد قرار گیرند تا درخواست های بزرگ تخصیص شی را برآورده کنند. اشیاء مرده مجاور در یک شیء آزاد ساخته می شوند.

چارچوب . NET Core و . NET (با شروع . NET Framework 4. 5. 1) شامل ویژگی GCSettings. largeobjecTheapCompactionMode است که به کاربران امکان می دهد مشخص کنند که LOH باید در طول مسدود کردن کامل GC فشرده شود. و در آینده ، . NET ممکن است تصمیم بگیرد که LOH را بطور خودکار جمع کند. این بدان معنی است که ، اگر اشیاء بزرگی را اختصاص می دهید و می خواهید مطمئن شوید که آنها حرکت نمی کنند ، باز هم باید آنها را بچسبانید.

شکل 1 سناریویی را نشان می دهد که GC نسل 1 را بعد از نسل اول 0 GC که در آن OBJ1 و OBJ3 مرده اند ، تشکیل می دهد و نسل 2 را بعد از نسل اول 1 GC که OBJ2 و OBJ5 مرده اند تشکیل می دهد. توجه داشته باشید که این و ارقام زیر فقط برای اهداف تصویرگری است. آنها حاوی اشیاء بسیار کمی هستند تا بهتر نشان دهند که چه اتفاقی در پشته می افتد. در واقعیت ، بسیاری از اشیاء دیگر به طور معمول در GC درگیر هستند.

Figure 1: A gen 0 GC and a gen 1 GC

شکل 1: یک نسل 0 و یک نسل 1 GC.

شکل 2 نشان می دهد که پس از یک Generation 2 GC که دید که OBJ1 و OBJ2 مرده اند ، GC فضای آزاد متناقض را از حافظه ای که قبلاً توسط OBJ1 و OBJ2 اشغال شده بود ، تشکیل می دهد ، که پس از آن برای برآورده کردن درخواست تخصیص OBJ4 استفاده می شد. فضای بعد از آخرین شی ، OBJ3 ، برای پایان بخش نیز می تواند برای برآورده کردن درخواست های تخصیص استفاده شود.

Figure 2: After a gen 2 GC

شکل 2: پس از یک نسل 2 GC

اگر فضای آزاد کافی برای پذیرش درخواست های بزرگ تخصیص شیء وجود نداشته باشد ، GC ابتدا سعی می کند بخش های بیشتری را از سیستم عامل بدست آورد. در صورت عدم موفقیت ، به امید آزاد کردن برخی از فضا ها ، یک Gener 2 GC را تحریک می کند.

در طی یک نسل 1 یا Generation 2 GC ، جمع کننده زباله بخش هایی را منتشر می کند که با فراخوانی عملکرد VirtualFree ، اشیاء زنده روی آنها به سیستم عامل باز می گردند. فضا پس از آخرین شیء زنده تا انتهای بخش از بین می رود (به جز در بخش زودگذر که Gen0/Gen1 در آن زندگی می کند ، جایی که جمع کننده زباله ها برخی را متعهد می کند زیرا برنامه شما بلافاصله در آن تخصیص می یابد). و فضاهای آزاد هرچند که مجدداً تنظیم می شوند ، متعهد هستند ، به این معنی که سیستم عامل نیازی به نوشتن داده ها در آنها به دیسک ندارد.

از آنجا که LOH فقط در طول GENER GENER 2 GC جمع آوری می شود ، بخش LOH فقط در طی چنین GC می تواند آزاد شود. شکل 3 سناریویی را نشان می دهد که جمع کننده زباله ها یک بخش (بخش 2) را به سیستم عامل باز می گرداند و فضای بیشتری را در بخش های باقیمانده از بین می برد. اگر برای برآورده کردن درخواست های بزرگ تخصیص شیء نیاز به استفاده از فضای تخریب شده در انتهای بخش دارد ، دوباره حافظه را مرتکب می شود.(برای توضیح در مورد تعهد/تجزیه ، به مستندات VirtualAlloc مراجعه کنید.

Figure 3: LOH after a gen 2 GC

شکل 3: LOH پس از یک نسل 2 GC

چه زمانی یک شی بزرگ جمع می شود؟

به طور کلی ، یک GC تحت یکی از سه شرط زیر اتفاق می افتد:

تخصیص از نسل 0 یا آستانه شی بزرگ فراتر می رود.

آستانه خاصیت یک نسل است. آستانه ای برای یک نسل تنظیم می شود که جمع کننده زباله اشیاء را به داخل آن اختصاص می دهد. هنگامی که از آستانه فراتر رود ، یک GC بر روی آن نسل ایجاد می شود. وقتی اشیاء کوچک یا بزرگ را اختصاص می دهید ، به ترتیب نسل 0 و آستانه LOH را مصرف می کنید. هنگامی که جمع کننده زباله ها به نسل 1 و 2 اختصاص می یابد ، آستانه آنها را مصرف می کند. این آستانه ها با اجرای برنامه به صورت پویا تنظیم می شوند.

این مورد معمولی است ؛بیشتر GC ها به دلیل تخصیص در پشته مدیریت شده اتفاق می افتد.

روش GC. Collect نامیده می شود.

اگر روش GC. Collect () بدون پارامتر یا اضافه بار دیگر به عنوان یک استدلال به عنوان یک آرگومان اضافه شود.

سیستم در وضعیت کم حافظه قرار دارد.

این اتفاق زمانی رخ می دهد که جمع کننده زباله از سیستم عامل اطلاع رسانی حافظه بالایی دریافت می کند. اگر جمع کننده زباله فکر کند که انجام یک Generation 2 GC تولیدی خواهد بود ، یکی را تحریک می کند.

پیامدهای عملکرد LOH

تخصیص بر روی عملکرد تأثیر پشته شیء بزرگ به روش های زیر.

CLR این تضمین را می کند که حافظه برای هر شیء جدیدی که می دهد پاک می شود. این بدان معناست که هزینه تخصیص یک شیء بزرگ تحت تأثیر پاکسازی حافظه قرار می گیرد (مگر اینکه باعث ایجاد GC شود). اگر برای پاک کردن یک بایت دو چرخه طول بکشد ، برای پاک کردن کوچکترین جسم بزرگ ، 170،000 چرخه طول می کشد. پاکسازی حافظه یک شیء 16 مگابایتی در دستگاه 2 گیگاهرتز تقریباً 16 میلی ثانیه طول می کشد. این یک هزینه نسبتاً بزرگ است.

از آنجا که LOH و Generation 2 با هم جمع می شوند ، اگر از هر یک از آستانه شخص فراتر رود ، یک مجموعه Generation 2 ایجاد می شود. اگر یک مجموعه Generation 2 به دلیل LOH آغاز شود ، نسل 2 لزوما پس از GC بسیار کوچکتر نخواهد بود. اگر داده های زیادی در مورد نسل 2 وجود نداشته باشد ، این تأثیر حداقل دارد. اما اگر نسل 2 بزرگ باشد ، در صورت ایجاد بسیاری از GC های نسل 2 GC ، می تواند باعث ایجاد مشکلات عملکرد شود. اگر بسیاری از اشیاء بزرگ به صورت موقت اختصاص داده می شوند و شما یک SOH بزرگ دارید ، می توانید زمان زیادی را برای انجام GCS صرف کنید. علاوه بر این ، در صورت تخصیص و رها کردن اشیاء واقعاً بزرگ ، هزینه تخصیص واقعاً می تواند اضافه شود.

عناصر آرایه با انواع مرجع.

اشیاء بسیار بزرگ در LOH معمولاً آرایه ها هستند (بسیار نادر است که یک شیء نمونه داشته باشیم که واقعاً بزرگ باشد). اگر عناصر یک آرایه غنی از مرجع باشند، هزینه ای متحمل می شود که اگر عناصر غنی از مرجع نباشند، وجود ندارد. اگر عنصر حاوی هیچ مرجعی نباشد، جمع آورنده زباله اصلاً نیازی به عبور از آرایه ندارد. به عنوان مثال، اگر از یک آرایه برای ذخیره گره ها در یک درخت باینری استفاده می کنید، یکی از راه های پیاده سازی آن این است که به گره راست و چپ گره توسط گره های واقعی مراجعه کنید:

اگر num_nodes بزرگ باشد، جمع کننده زباله باید حداقل از دو مرجع برای هر عنصر عبور کند. یک روش جایگزین، ذخیره شاخص گره های راست و چپ است:

به جای ارجاع داده های گره چپ به عنوان left. d، شما به آن به عنوان binary_tr[left_index]. d اشاره می کنید. و زباله گرد نیازی به بررسی هیچ مرجعی برای گره چپ و راست ندارد.

از بین سه عامل، دو عامل اول معمولاً مهمتر از عامل سوم هستند. به همین دلیل، توصیه می کنیم به جای تخصیص اشیاء موقت، مجموعه ای از اشیاء بزرگ را که دوباره از آنها استفاده می کنید، اختصاص دهید.

داده های عملکرد را برای LOH جمع آوری کنید

قبل از جمع آوری داده های عملکرد برای یک منطقه خاص، باید قبلاً موارد زیر را انجام داده باشید:

شواهدی پیدا شد که نشان می دهد باید به این منطقه نگاه کنید.

بخش های دیگری را که می شناسید، بدون یافتن چیزی که بتواند مشکل عملکردی که دیدید را توضیح دهد، خسته کرد.

برای اطلاعات بیشتر در مورد اصول حافظه و CPU، قبل از اینکه بخواهید راه حلی پیدا کنید، به وبلاگ Understand the problem مراجعه کنید.

می توانید از ابزارهای زیر برای جمع آوری داده ها در مورد عملکرد LOH استفاده کنید:

شمارنده های عملکرد حافظه دات نت CLR

این شمارشگرهای عملکرد معمولا اولین گام خوبی برای بررسی مسائل مربوط به عملکرد هستند (اگرچه توصیه می کنیم از رویدادهای ETW استفاده کنید). همانطور که در شکل 4 نشان داده شده است، شما با اضافه کردن شمارنده های مورد نظر خود، مانیتور عملکرد را پیکربندی می کنید. مواردی که برای LOH مرتبط هستند عبارتند از:

مجموعه های Gen 2

تعداد دفعاتی که GCهای نسل 2 از زمان شروع فرآیند رخ داده اند را نشان می دهد. شمارنده در پایان مجموعه نسل 2 (همچنین به نام مجموعه زباله کامل) افزایش می یابد. این شمارنده آخرین مقدار مشاهده شده را نمایش می دهد.

اندازه هیپ شی بزرگ

اندازه فعلی LOH را بر حسب بایت، از جمله فضای خالی، نمایش می دهد. این شمارنده در پایان جمع آوری زباله به روز می شود، نه در هر تخصیص.

یک روش مشترک برای بررسی پیشخوان های عملکرد با Monitor Performance (Perfmon. exe) است. برای اضافه کردن پیشخوان جالب برای فرآیندی که به آنها اهمیت می دهید ، از "Add Counters" استفاده کنید. همانطور که شکل 4 نشان می دهد می توانید داده های پیشخوان عملکرد را در یک پرونده ورود ذخیره کنید:

Screenshot that shows adding performance counters.

شکل 4: LOH پس از یک نسل 2 GC

پیشخوان های عملکرد نیز می توانند به صورت برنامه ای پرس و جو شوند. بسیاری از افراد آنها را به عنوان بخشی از روند آزمایش معمول خود جمع می کنند. هنگامی که آنها پیشخوان ها را با مقادیری که خارج از حد معمول است ، مشاهده می کنند ، از وسایل دیگری برای دریافت داده های دقیق تر برای کمک به تحقیقات استفاده می کنند.

ما توصیه می کنیم که به جای پیشخوان های عملکرد از رویدادهای ETW استفاده کنید ، زیرا ETW اطلاعات غنی تری را ارائه می دهد.

رویدادهای ETW

جمع کننده زباله ها مجموعه ای غنی از رویدادهای ETW را برای کمک به شما در درک اینکه پشته چه کاری انجام می دهد و چرا فراهم می کند. پست های وبلاگ زیر نحوه جمع آوری و درک رویدادهای GC را با ETW نشان می دهد:

برای شناسایی بیش از حد نسل 2 GC ناشی از تخصیص موقت LOH ، به ستون دلیل دلیل برای GCS نگاه کنید. برای یک آزمایش ساده که فقط اشیاء بزرگ موقت را اختصاص می دهد ، می توانید اطلاعات مربوط به رویدادهای ETW را با خط فرمان perfview زیر جمع کنید:

نتیجه چیزی شبیه به این است:

Screenshot that shows ETW events in PerfView.

شکل 5: رویدادهای ETW نشان داده شده با استفاده از perfview

همانطور که مشاهده می کنید ، همه GC ها Generation 2 GC هستند و همه آنها توسط Alloclarge ایجاد می شوند ، به این معنی که تخصیص یک جسم بزرگ باعث ایجاد این GC می شود. ما می دانیم که این تخصیص ها موقتی هستند زیرا ستون میزان بقا ٪ LOH 1 ٪ می گوید.

شما می توانید رویدادهای ETW اضافی را جمع کنید که به شما می گوید چه کسی این اشیاء بزرگ را اختصاص داده است. خط فرمان زیر:

یک رویداد AllocationTick را جمع آوری می کند ، که تقریباً در هر 100 کیلو ارزش تخصیص شلیک می شود. به عبارت دیگر ، هر بار که یک شیء بزرگ اختصاص می یابد ، یک رویداد اخراج می شود. سپس می توانید به یکی از نماهای تخصیصی GC Heap ، که به شما نشان می دهد CALLSTACKS که به اشیاء بزرگ اختصاص داده شده است ، نگاه کنید:

Screenshot that shows a garbage collector heap view.

شکل 6: نمای تخصیصی GC Heap

همانطور که مشاهده می کنید ، این یک آزمایش بسیار ساده است که فقط اشیاء بزرگ را از روش اصلی آن اختصاص می دهد.

یک اشکال زدایی

اگر تمام آنچه شما دارید یک دامنه حافظه است و باید به آنچه در واقع اشیاء در LOH هستند ، نگاه کنید ، می توانید از پسوند SOS Debugger ارائه شده توسط . NET استفاده کنید.

دستورات اشکال زدایی ذکر شده در این بخش برای اشکال زدایی ویندوز قابل اجرا است.

موارد زیر خروجی نمونه را از تجزیه و تحلیل LOH نشان می دهد:

اندازه پشته LOH (16, 754, 224 + 16, 699, 288 + 16, 284, 504) = 49, 738, 016 بایت است. بین آدرس های 023e1000 و 033db630، 8،008،736 بایت توسط آرایه ای از System. Object اشیاء، 6،663،696 بایت توسط آرایه ای از اشیاء System. Byte اشغال می شود، و 2008،736 بایت فضای خالی است.

گاهی اوقات، دیباگر نشان می دهد که اندازه کل LOH کمتر از 85000 بایت است. این اتفاق می افتد زیرا زمان اجرا خود از LOH برای تخصیص برخی از اشیاء کوچکتر از یک شی بزرگ استفاده می کند.

از آنجایی که LOH متراکم نیست، گاهی اوقات تصور می شود که LOH منبع تکه تکه شدن است. تکه تکه شدن یعنی:

تکه تکه شدن پشته مدیریت شده، که با مقدار فضای آزاد بین اشیاء مدیریت شده نشان داده می شود. در SoS، دستور ! dumpheap type Free مقدار فضای خالی بین اشیاء مدیریت شده را نمایش می دهد.

تکه تکه شدن فضای آدرس حافظه مجازی (VM) که حافظه ای است که به عنوان MEM_FREE علامت گذاری شده است. شما می توانید آن را با استفاده از دستورات مختلف دیباگر در windbg دریافت کنید.

مثال زیر تکه تکه شدن در فضای VM را نشان می دهد:

بیشتر دیده می شود که تکه تکه شدن VM توسط اشیاء بزرگ موقتی ایجاد می شود که جمع کننده زباله نیاز دارد که به طور مکرر بخش های پشته مدیریت شده جدیدی را از سیستم عامل دریافت کند و قسمت های خالی را به سیستم عامل بازگرداند.

برای بررسی اینکه آیا LOH باعث تکه تکه شدن VM می شود، می توانید یک نقطه شکست در VirtualAlloc و VirtualFree تعیین کنید تا ببینید چه کسی آنها را فراخوانده است. به عنوان مثال، برای اینکه ببینید چه کسی سعی کرده تکه های حافظه مجازی بزرگتر از 8 مگابایت را از سیستم عامل اختصاص دهد، می توانید یک نقطه شکست مانند زیر تعیین کنید:

این دستور به اشکال زدا نفوذ می کند و پشته تماس را فقط در صورتی نشان می دهد که VirtualAlloc با اندازه تخصیص بیشتر از 8 مگابایت (0x800000) فراخوانی شود.

CLR 2. 0 یک ویژگی به نام VM Hoarding اضافه کرد که می تواند برای سناریوهایی که بخش ها (از جمله روی انبوه شی های بزرگ و کوچک) اغلب به دست می آیند و منتشر می شوند، مفید باشد. برای تعیین VM Hoarding، یک پرچم راه اندازی به نام STARTUP_HOARD_GC_VM را از طریق API میزبان مشخص می کنید. CLR به جای اینکه بخش های خالی را به سیستم عامل بازگرداند، حافظه این بخش ها را غیرفعال می کند و آنها را در لیست آماده به کار قرار می دهد.(توجه داشته باشید که CLR این کار را برای بخش هایی که خیلی بزرگ هستند انجام نمی دهد.) CLR بعداً از آن بخش ها برای برآورده کردن درخواست های بخش جدید استفاده می کند. دفعه بعد که برنامه شما به یک بخش جدید نیاز دارد، CLR از یکی از این لیست آماده به کار استفاده می کند اگر بتواند قسمتی را پیدا کند که به اندازه کافی بزرگ باشد.

احتکار VM همچنین برای برنامه هایی که می خواهند بخش هایی را که قبلاً به دست آورده اند، نگه دارند، مانند برخی از برنامه های سرور که برنامه های غالب در حال اجرا در سیستم هستند، برای جلوگیری از استثناهای خارج از حافظه مفید است.

اکیداً توصیه می کنیم هنگام استفاده از این ویژگی برنامه خود را به دقت آزمایش کنید تا مطمئن شوید که برنامه شما از حافظه نسبتاً پایداری استفاده می کند.

ارزهای دیجیتال...
ما را در سایت ارزهای دیجیتال دنبال می کنید

برچسب : نویسنده : مریم پالیزبان بازدید : <-PostHit-> تاريخ : جمعه 4 فروردين 1402 ساعت: 19:35