فی مصائب نشت حافظه در اندروید یا مموری لیک چیست؟(بخش اول)

نشت حافظه یا مموری لیک یکی از مهمترین مشکلاتی است که معمولا در برنامه نویسی اندروید باهاش دست و پنجه نرم میکنیم ولی خب قبل از اینکه بریم سراغش باید یه نگاهی به کل موضوع بندازیم بعد از اون برم سراغ جاهایی که معمولا نشت حافظه اتفاق میافته و چطوری میشه ازش جلوگیری کرد پس قبل از هرچیزی باید یه سری تعاریف درباره این مشکل داشته باشیم بعد کم کم بریم سراغ بقیه اش بخش اول این مقاله درباره همین موارد هست و اینکه چرا نشت حافطه بده و اصلا چرا اتفاق میافته .

مموری لیک اونقدر موضوع کلیدی و مهمی در امر توسعه هست که میتونه مرز بین یه دولوپر خوب و یه دولوپر بد باشه و البته از مسائلی است که خیلی از توسعه دهنده ها باهاش درگیرن پس اگر باهاش درگیر شدین یا در کارتون باهاش برخورد داشتین اعتماد به نفستون رو از دست ندین این اتفاق ممکنه برای هر برنامه نویسی پیش بیاد مهم اینه چطوری مدیریت شه و مشکل رو حل کنید معمولا بحث پرفرمنس و کیفیت کار از اون مباحثی هست که خیلی بهش پرداخته نمیشه خیلی وقتها درست کار کردن اپ شما تنها چیزیه که ازتون میخوان پس بحث پرفرمنس به حاشیه میره موضوعی که واقعا میتونه باعث دردسر بشه و یکی از مهمترین بخش ها در تولید اپ همین بحث مدیریت اشیاء در حافظه است

Garbage Collector

خوشبختانه جاوا از زبان هایی هست که مدیریت حافظه رو به خوبی انجام میده نه تنها اختصاص و عدم تخصیص حافظه رو انجام میده بلکه میتونه بدون زحمت خاصی اون منبعی رو که نیاز ندارید از برنامه تون پس بگیره شاید این سوال براتون پیش اومده که خب چرا ما باید درگیر مدیریت حافظه باشیم وقتی چیزی مثل Garbage Collector داریم اگر مشکلی تو مدیریت حافظه بوجود بیاد تقصیر ما نیست بلکه مقصر اصلی جاواست!

خب بهتره یکم موضع رو باز تر کنم تا متوجه بشید چرا مموری لیک بوجود میاد و اینکه Garbage Collector دوستی هست که سعی داره خرابکاری های مارو تمیز کنه نه مقصر خرابکاری هایی که ما توی برنامه مون داریم

Garbage Collector
Garbage Collector

خب Garbage Collector میاد و نقطه ابتدایی تخصیص حافظه رو به عنوان نقطه اصلی یا root در نظر میگیره تمامی اشیائی که به طور مستقیم یا غیر مستقیم به این پایه ها متصلن رو طی میکنه و حافظه ای که بهش نیاز نباشه رو از اشیاء پس میگیره ولی مشکل از اونجایی شروع میشه که یه سری از اشیاء ساخته شده توسط GC طی نمیشن یا به عبارتی Garbage Collector اونها رو دیدار نمیکنه پس ما به محض اجرای GC یه درخت از حافظه داریم که از نقطه root و سرشاخه شروع میشه یک ملاقات کننده داریم که از تمام گره های مرتبط تا پایین ترین برگ های درخت پیش میره ولی تعدادی از گره های اصطلاحا مرده وجود داره که ملاقات کننده نمیتونه اون ها رو ملاقات کنه و همین موضوع باعث میشه که ما توی برنامه مون آشغال جا بذاریم تقریبا شبیه کاری که خیلیا تو جنگل و دریا و جاهای دیگه میکنن و نتیجه چیزی شبیه تصویر پایین میشه

خب برای مطالعه بیشتر درباره GCدر جاوا اینکه چه زمانی اجرا میشه و....میتونید به اینجا و اینجا مراجعه کنید

مفهوم نشت حافظه

خب حالا که یه دید کلی درباره مدیریت حافظه در جاوا داریم شاید بتونیم ساده ترین تعریف از نشت حافظه رو ارائه بدیم

"نشت حافظه زمانی اتفاق می افتاد که ما یک شئ را پس از آنکه به هدف ایجادش دست پیدا کردیم همچنان نگهداری کنیم"

فرض کنید که هدف یک بطری پلاستیکی آب معدنی حمل آب مورد نیاز شما در طول پیاده روی باشه بعد از اینکه آب بطری خالی شد و شما از پیاده روی برگشتین به هدف بوجود اومدن اون بطری دست پیدا کردید و دیگه بهش نیاز ندارید ولی اگر اونو توی اتاقتون نگهدارید و هرروز یک بطری جدید بهش اضافه کنید چی؟ این دقیقا همون اتفاقی هست که اگر مراقب نشت حافظه نباشیم توی اپلیکیشنمون میافته :)

اما قسمت خوب داستان اینه که نشت حافظه همیشه نمیتونه یه مصیبت بزرگ باشه بلکه با توجه به حجمی که توی حافظه ایجاد میکنه میتونه یه نگرانی بزرگ یا فقط یه اتفاق خیلی کوچیک و بی آزار باشه شما فرض کنید یک شی 9 کیلوبایت حافظه رو اشغال میکنه و توسط مدیرحافظه از بین نمیره تا اینجا چیز آزازدهنده ای نیست ولی اگر اون شی 500 کیلوبایت رو بگیره و درون حلقه ای ایجاد بشه که 600 بار میچرخه چی؟ هنوزم میشه بهش اهمیت نداد؟
شاید باورش سخت باشه ولی توی فریم ورک خود اندروید هم گاهی از این نشت حافظه های خیلی کوچولو وجود داره که تاثیر زیادی روی کل عملکرد ندارن و میشه در کمال آرامش ازشون رد شد چون گاهی تغییر ساختار و رفع مشکل هزینه ای به مراتب بیشتر داره تا مثلا یک نشت 15 کیلوبایتی !

اما باز هم تاکید میکنم نشت حافظه میتونه شما رو به معنای واقعی کلمه به زانو دربیاره و کار کردن روی پروژه براتون شبیه کار کردن تو جهنم بشه پس باید خیلی مراقبش باشید چون هیچکس نمیخواد با اپلیکیشن پر از لگ کند کار کنه که هر لحظه ممکنه کرش کنه خواه ناخواه این موضوع میتونه باعث ریزش شدید کاربرهاتون بشه

زمانی که اپلیکیشن شما مموری لیک یا نشت حافظه داشته باشه وقتی کاربر ازش استفاده میکنه مرتب هیپ حافظه رو پر میکنه و از جایی که GC توانایی خالی کردن هیپ حافظه اپ رو نداره نهایتا به جایی میرسیم که فضایی برای اختصاص دهی وجود نداره و فاجعه اتفاق میافته اپ شما کرش میکنه و از کار میافته از طرفی GC برای پردازش توان زیادی از سی پی یو میگیره همین موضوع میتونه اپ شما رو کند کنه پس هرچه کمتر به GC نیاز داشته باشید و هرچه کمتر اجرا شه توان کمتری از پردازشگر صرف میشه و به همین سادگی امکان ایجاد لگ در اپ شما کاهش پیدا میکنه حالا این در شرایط استفاده از GC های کوچک هست اگر heap نتونه فضای خالی ایجاد کنه مرتب GC های بزرگتر با نیاز پردازشی بالاتری اجرا میشه و این به وضوح میتونه اپ شما رو کند و لگی کنه و اساسا تمام تجربه کاربری شما رو تحت تاثیر قرار بده

فکر کنم تا اینجا به خوبی مشخص شده که چرا ما باید حتما فکری به حال نشت حافظه بکنیم و چرا همین موضوع میتونه مرز بین توسعه دهنده خوب و بد باشه

نحوه تشخیص نشت حافظه در اندروید

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

  1. Android Studio Profiler

ابزار مانیتورینگ اندروید استودیو که در زمان اجرای برنامه میتونید علاوه بر مانیتورینگ حافظه مقدار استفاده از CPU و GPU و حتی ارتباط با شبکه رو روی نمودار ببینید میتونید استفاده از حافظه و پر و خالی شدن حافظه رو تو بخش های مختلف برنامه بررسی کنید و میتونه ابزار خوبی باشه برای تشخیص پر و خالی شدن حافظه خصوصا زمانی که اپ رو به حالت بکراند ببرید و مقدار مصرف حافظه و خالی شدن حافظه رو ببینید

۲. Leak Canary

خب اینم از ابداعات حاصل از تنبلی مهندسان کامپیوتر هست تقریبا هرچیزی که برای پیدا کردن نشت حافظه نیاز دارید درون این کتابخونه هست در کنار اپ شما اجرا میشه هرجا نیاز باشه از حافظه DUMP میگیره و جاهایی که پتانسیل نشت دارن رو به صورت نوتیفیکیشن براتون ارسال میکنه و از همه مهمتر محل نشت رو trace میکنه تا بهتون کمک کنه محل اصلی نشت رو پیدا و اصلاح کنید بهتون پیشنهاد میکنم حتما ازش استفاده کنید و یه جای اختصاصی برای تست کردن زمان کد نوشتن براش در نظر بگیرید

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

مرسی از وقتی که به خوندن اختصاص دادید ممنون میشه اگر انتقادی بود کامنت بذارید

بخش دوم مقاله مموری لیک در اندروید <br/>