মেমোরি ম্যানেজমেন্ট ও লিক প্রতিরোধ
এই পাঠে যা শিখবেন
- মোবাইলে মেমোরি লিক কী এবং কেন এটি ওয়েবের চেয়ে বেশি ক্ষতিকর হতে পারে
- সাবস্ক্রাইবার cleanup না করলে কীভাবে একটি শেয়ার্ড স্টোরে ক্রমাগত রেফারেন্স জমে যায় — বাস্তব গণনাসহ
- স্ক্রিন থেকে বের হওয়ার সময় সঠিকভাবে
unsubscribe()কল করলে কীভাবে এই বৃদ্ধি বন্ধ হয় - মেমোরি লিকের অন্যান্য সাধারণ উৎস (টাইমার, স্ট্যাটিক রেফারেন্স, বড় ক্লোজার)
১ · মোবাইলে মেমোরি লিক কেন বিশেষভাবে ক্ষতিকর
একটি ডেস্কটপ/সার্ভার অ্যাপে কয়েক মেগাবাইট লিক হয়তো লক্ষ্যণীয় নয়, কিন্তু একটি মোবাইল ডিভাইসে RAM সীমিত এবং
একাধিক অ্যাপ একসাথে চলে। ক্রমাগত জমতে থাকা মেমোরি লিক ধীরে ধীরে অ্যাপের ব্যবহৃত মেমোরি বাড়িয়ে দেয়, এবং একসময়
OS নিজেই মেমোরি বাঁচাতে অ্যাপটি জোর করে killed স্টেটে পাঠিয়ে দেয় (M1-এর লাইফসাইকেল মেশিনের সাথে
সরাসরি সম্পর্কিত)।
স্ক্রিন বন্ধ হওয়ার পরও একটি স্টোরে সাবস্ক্রাইব করা থেকে যাওয়া — এই পাঠের মূল দৃশ্য।
একটি টাইমার/ইন্টারভাল যা স্ক্রিন বন্ধ হওয়ার পরও চলতে থাকে, স্ক্রিনের রেফারেন্স ধরে রাখে।
একটি স্ক্রিন/ভিউয়ের রেফারেন্স ভুলবশত একটি দীর্ঘজীবী গ্লোবাল ভ্যারিয়েবলে সংরক্ষিত থেকে যাওয়া।
২ · লিকি ভার্সন — cleanup ছাড়া সাবস্ক্রাইব
নিচে একটি শেয়ার্ড Store আছে যাতে একাধিক স্ক্রিন সাবস্ক্রাইব করতে পারে। enter_screen_leaky
ফাংশনটি স্ক্রিনে ঢোকার সময় একটি নতুন লিসেনার সাবস্ক্রাইব করে — কিন্তু স্ক্রিন থেকে বের হওয়ার সময় কোনো
unsubscribe কল করা হয় না। একই স্ক্রিনে ৫ বার ঢোকা-বের হওয়ার (enter/exit) সিকোয়েন্স সিমুলেট করে দেখা যাক
সাবস্ক্রাইবার তালিকার দৈর্ঘ্য কী হয়।
# একাধিক স্ক্রিন যে শেয়ার্ড স্টোরে সাবস্ক্রাইব করতে পারে
class Store:
def __init__(self):
self.subscribers = []
def subscribe(self, listener):
self.subscribers.append(listener)
def unsubscribe(self, listener):
if listener in self.subscribers:
self.subscribers.remove(listener)
def enter_screen_leaky(store):
# স্ক্রিনে ঢোকার সময় একটি নতুন লিসেনার তৈরি করে সাবস্ক্রাইব করা হয়...
def on_update(new_state):
pass # ডেটা বদলালে স্ক্রিন রিফ্রেশ করার কল্পিত লজিক
store.subscribe(on_update)
# ...কিন্তু স্ক্রিন থেকে বের হওয়ার সময় unsubscribe() কল করা হয় না -- এখানেই লিক তৈরি হয়
leaky_store = Store()
for cycle in range(1, 6):
enter_screen_leaky(leaky_store) # ব্যবহারকারী স্ক্রিনে ঢুকলো, তারপর বের হয়ে গেলো (কোনো cleanup ছাড়াই)
print(f"সাইকেল {cycle}: enter+exit করার পর subscriber সংখ্যা = {len(leaky_store.subscribers)}")
print(f"\n৫ বার স্ক্রিনে ঢোকা-বের হওয়ার পর মোট subscriber: {len(leaky_store.subscribers)} (ক্রমাগত বাড়তেই থাকবে)")
subscribers তালিকার দৈর্ঘ্য ১ করে বাড়ে — ৫ সাইকেল পর তা ৫-এ পৌঁছায়।
বাস্তব অ্যাপে ব্যবহারকারী শত শত বার একটি স্ক্রিনে ঢুকতে-বের হতে পারেন, তাই এই তালিকা কার্যত সীমাহীন বাড়তে
থাকবে — এটিই মেমোরি লিক।
৩ · সঠিক ভার্সন — বের হওয়ার সময় unsubscribe
সমাধান সহজ কিন্তু critical: স্ক্রিনে ঢোকার সময় যে লিসেনারটি সাবস্ক্রাইব করা হয়েছিল, স্ক্রিন থেকে বের হওয়ার সময় ঠিক সেই একই লিসেনারকে unsubscribe করতে হবে। নিচের ভার্সনটি লিসেনারের রেফারেন্স ফেরত দেয়, যাতে exit-এর সময় সঠিকভাবে সরানো যায়।
class Store:
def __init__(self):
self.subscribers = []
def subscribe(self, listener):
self.subscribers.append(listener)
def unsubscribe(self, listener):
if listener in self.subscribers:
self.subscribers.remove(listener)
def enter_screen_fixed(store):
def on_update(new_state):
pass
store.subscribe(on_update)
return on_update # কলারকে লিসেনারটি ফেরত দেওয়া হয়, যাতে exit-এ ঠিক এটিকেই unsubscribe করা যায়
def exit_screen_fixed(store, listener):
store.unsubscribe(listener) # স্ক্রিন থেকে বের হওয়ার সময় সঠিকভাবে cleanup
fixed_store = Store()
for cycle in range(1, 6):
listener = enter_screen_fixed(fixed_store) # স্ক্রিনে ঢোকা
exit_screen_fixed(fixed_store, listener) # স্ক্রিন থেকে বের হওয়া -- cleanup সহ
print(f"সাইকেল {cycle}: enter+exit করার পর subscriber সংখ্যা = {len(fixed_store.subscribers)}")
print(f"\n৫ বার স্ক্রিনে ঢোকা-বের হওয়ার পর মোট subscriber: {len(fixed_store.subscribers)} (অপরিবর্তিত থাকে)")
subscribers তালিকার দৈর্ঘ্য ঠিক ০-তেই থেকে যায় — কারণ
প্রতিটি enter-এর সাথে একটি সঠিক exit জোড়া বাঁধানো আছে। এই একই lifecycle-ভিত্তিক ধারণা M7-এর L29-এ
foreground/background ট্রানজিশনের সাথে যুক্ত করে দেখানো হয়েছিল — এখানে সেটিকে
বিশুদ্ধভাবে "মেমোরি লিক প্রতিরোধ"-এর দৃষ্টিকোণ থেকে দেখা হলো।
প্রতিটি subscribe()/সংযুক্তির জন্য একটি নির্দিষ্ট, প্রতিসম unsubscribe() থাকা উচিত —
এবং সেটি অবশ্যই স্ক্রিনের জীবনচক্রের সাথে সংযুক্ত থাকতে হবে (ঢোকার সময় সাবস্ক্রাইব, বের হওয়ার সময়
unsubscribe)। উপরের দুটি সিমুলেশন প্রমাণ করে যে এই একটি ছোট শৃঙ্খলাই একটি সীমাহীন-বর্ধনশীল লিককে একটি
সম্পূর্ণ স্থিতিশীল, শূন্য-বৃদ্ধি ব্যবস্থায় পরিণত করে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
enter_screen_leaky-তে যদি প্রতিবার একই লিসেনার ফাংশন (নতুন করে সংজ্ঞায়িত না করে) পাঠানো হতো, তাহলেও কি লিক হতো?
হ্যাঁ যদি সেটি এখনো unsubscribe না করা হয় — তবে সেক্ষেত্রে subscribers তালিকায় একই ফাংশন
একাধিকবার থাকতো, ফলে একটি স্টেট আপডেটে সেই একই লিসেনার বহুবার কল হতো (ডুপ্লিকেট নোটিফিকেশন)। মূল সমস্যা
"নতুন ফাংশন" তৈরি হওয়া নয় — সমস্যা হলো unsubscribe না হওয়া, তালিকা থেকে কখনোই না সরানো।
প্র ০২ মেমোরি লিক কেন সাথে সাথে ক্র্যাশ ঘটায় না, বরং ধীরে ধীরে সমস্যা তৈরি করে?
প্রতিটি একক লিক (যেমন একটি ফাংশন রেফারেন্স) সাধারণত খুবই ছোট — কয়েক বাইট থেকে কয়েক কিলোবাইট। একবার ঘটলে তা লক্ষ্যণীয় নয়। কিন্তু ব্যবহারকারী যতবার স্ক্রিনে ঢোকেন-বের হন, ততবার তা জমতে থাকে — সময়ের সাথে সাথে ক্রমবর্ধমান মেমোরি ব্যবহার একসময় OS-কে অ্যাপ বন্ধ করতে বাধ্য করে। এটি একটি "ধীর রক্তক্ষরণ" জাতীয় সমস্যা, তাৎক্ষণিক ক্র্যাশ নয়।
প্র ০৩
দ্বিতীয় কোড সেলে enter_screen_fixed লিসেনারটি return করে কেন — সরাসরি ভেতরেই unsubscribe করলে হতো না?
না, কারণ enter_screen_fixed কল হওয়ার মুহূর্তেই স্ক্রিন ঢোকে, কিন্তু exit ঘটে অনেক পরে —
কখন ঘটবে তা ফাংশনটি নিজে জানে না। তাই লিসেনার রেফারেন্সটি কলারের কাছে ফেরত দেওয়া হয়, যাতে যখনই সত্যিকারের
exit ঘটে (ভবিষ্যতের একটি ভিন্ন মুহূর্তে), কলার ঠিক সেই নির্দিষ্ট লিসেনারটিকেই unsubscribe()
করতে পারে।
অনুশীলন
-
চিন্তা করুন: একটি অ্যাপের একটি "নোটিফিকেশন ব্যাজ কাউন্ট" ফিচার কল্পনা করুন যা একটি গ্লোবাল
স্টোরে সাবস্ক্রাইব করে আপডেট হয়, এবং এটি অ্যাপের প্রতিটি স্ক্রিনে (হেডারে) দেখানো হয়। এই ফিচারের জন্য কি
unsubscribe করার দরকার আছে?
যদি এই সাবস্ক্রিপশনটি পুরো অ্যাপের জীবনকালব্যাপী (একবার তৈরি হয়ে অ্যাপ চলাকালীন সবসময় সক্রিয় থাকা দরকার) হয়, তাহলে বারবার সাবস্ক্রাইব/আনসাবস্ক্রাইব করার দরকার নেই — এটি ইচ্ছাকৃতভাবে দীর্ঘজীবী। সমস্যা তখনই হয় যখন একটি সাবস্ক্রিপশনের জীবনকাল একটি নির্দিষ্ট, ক্ষণস্থায়ী স্ক্রিনের সাথে বাঁধা থাকা উচিত ছিল, কিন্তু ভুলবশত তা কখনো পরিষ্কার হয় না।
-
পরীক্ষা করুন: লিকি কোড সেলে লুপটি
range(1, 6)-এর বদলেrange(1, 11)করুন (৫ বারের বদলে ১০ বার enter/exit), তারপর চালিয়ে দেখুন চূড়ান্ত subscriber সংখ্যা কত হয়।প্রতিটি সাইকেলে একটি করে নতুন লিসেনার যোগ হয় এবং কখনো সরানো হয় না, তাই ১০ সাইকেল পর
len(leaky_store.subscribers)হবে ঠিক ১০ — সাইকেল সংখ্যার সাথে সরাসরি সমানুপাতিক বৃদ্ধি, যা লিকের রৈখিক (linear) প্রকৃতি নিশ্চিত করে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, নেটওয়ার্কিং, ডিভাইস ফিচার, পারফরম্যান্স ও ডিপ্লয়মেন্ট — সব একসাথে।
- পরের পাঠ: ব্যাটারি ও রিসোর্স অপ্টিমাইজেশন L48 মেমোরির পাশাপাশি ব্যাটারি খরচও একটি বাস্তব সীমাবদ্ধ রিসোর্স — একটি সরল "power budget" মডেল দিয়ে দুটি ভিন্ন পলিসির খরচ তুলনা।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks ও Mobile App Development — সব এক জায়গায়।