നിങ്ങളുടെ ഉപയോക്താവിന്റെ ബ്രൗസർ ടാബ് മുപ്പത് മിനിറ്റിനുശേഷം ഫ്രീസ് ആകുന്നു. UI തടസ്സപ്പെടുന്നു. തുടർന്ന് ഔട്ട്-ഓഫ്-മെമ്മറി (out-of-memory) എറർ കാരണം ബ്രൗസർ ക്രാഷ് ആകുന്നു.

നിങ്ങൾ കോഡ് വരിവരിയായി പരിശോധിക്കുന്നുണ്ടെങ്കിലും തെറ്റായി ഒന്നും കാണുന്നില്ല. React-ലെ മെമ്മറി ലീക്കുകളുടെ (memory leaks) ഏറ്റവും മടുപ്പിക്കുന്ന ഭാഗമാണിത്. ബഗ് നിങ്ങളുടെ JSX സിന്റാക്സിലോ നിങ്ങളുടെ ഹുക്ക് (hook) ലോജിക്കിലോ അല്ല ഇരിക്കുന്നത്. അത് നിങ്ങളുടെ കമ്പോണന്റും ബ്രൗസറിലെ ഗാർബേജ് കളക്ടറും (garbage collector) തമ്മിലുള്ള ഇടവേളയിലാണ് നിലനിൽക്കുന്നത്. ഒരു അനാവശ്യ ഇവന്റ് ലിസണറോ (event listener) അല്ലെങ്കിൽ ദീർഘകാലം നിലനിൽക്കുന്ന ഒരു ക്ലോഷറോ (closure) മെമ്മറി തിരികെ എടുക്കുന്നതിൽ നിന്ന് കളക്ടറെ തടയുന്നു. നിങ്ങൾ ഒരു കമ്പോണന്റ് അൺമൗണ്ട് (unmount) ചെയ്യുന്നുണ്ടെങ്കിലും, നിലനിൽക്കുന്ന ഒരു റഫറൻസ് കാരണം മുഴുവൻ ട്രീയും ഹീപ്പിൽ (heap) നിലനിൽക്കുന്നു. കോഡ് വായിച്ചു കൊണ്ട് മാത്രം ഇത്തരം ലീക്കുകൾ കണ്ടെത്താൻ കഴിയില്ല. പ്രശ്നം ഉപരിതലത്തിന് താഴെ ഒളിഞ്ഞിരിക്കുന്നു, അത് കണ്ണുകൾക്ക് കാണാൻ കഴിയില്ല, യൂണിറ്റ് ടെസ്റ്റുകളിൽ (unit tests) നിശബ്ദമായിരിക്കും.

എന്തുകൊണ്ടാണ് ലീക്കുകൾ പെട്ടെന്ന് ശ്രദ്ധിക്കപ്പെടാതെ ഒളിഞ്ഞിരിക്കുന്നത്?

V8 പോലുള്ള JavaScript എൻജിനുകൾ മെമ്മറി സ്വയമേവ നിയന്ത്രിക്കുന്നു. ഒരു ഒബ്‌ജക്റ്റിലേക്ക് റൂട്ടിൽ (root) നിന്ന് ഒരു റഫറൻസ് പാത്തും ഇല്ലാതിരിക്കുമ്പോൾ, എൻജിൻ ആ ഒബ്‌ജക്റ്റിനെ ഗാർബേജ് ആയി അടയാളപ്പെടുത്തുകയും ആ സ്ഥലം തിരികെ എടുക്കുകയും ചെയ്യുന്നു. നിങ്ങൾ ഉദ്ദേശിച്ചതിനേക്കാൾ കൂടുതൽ സമയം ഒരു ഹിഡൻ റഫറൻസ് നിലനിൽക്കുന്നത് വരെ ഈ പ്രക്രിയ നന്നായി പ്രവർത്തിക്കും.

React-ൽ, കമ്പോണന്റുകളും DOM-ഉം തമ്മിലുള്ള അതിർത്തിയിലാണ് അപകടം പലപ്പോഴും സംഭവിക്കുന്നത്. ഒരു മോഡലിനുള്ളിൽ (modal) window-ൽ ഒരു resize ലിസണർ നിങ്ങൾ ഘടിപ്പിച്ചേക്കാം, അല്ലെങ്കിൽ ഒരു ഡാഷ്‌ബോർഡ് വിഡ്ജറ്റിൽ (dashboard widget) ഒരു WebSocket-ലേക്ക് സബ്‌സ്‌ക്രൈബ് ചെയ്തേക്കാം. ഉപയോക്താവ് മോഡൽ അടയ്ക്കുകയോ മറ്റൊരു പേജിലേക്ക് പോകുകയോ ചെയ്യുമ്പോൾ കമ്പോണന്റ് അൺമൗണ്ട് ആകുന്നു. എന്നാൽ സബ്‌സ്‌ക്രിപ്ഷൻ നിലനിൽക്കുകയാണെങ്കിൽ, ഗ്ലോബൽ window ഒബ്‌ജക്റ്റിൽ നിന്ന് നിങ്ങളുടെ ഹാൻഡ്‌ലറിലേക്കും, ഹാൻഡ്‌ലറിൽ നിന്ന് കമ്പോണന്റിന്റെ ക്ലോഷറിലേക്കും ഒരു സാധുവായ റഫറൻസ് എൻജിൻ കാണുന്നു. കമ്പോണന്റ്, അതിന്റെ പ്രോപ്‌സ് (props), അതിന്റെ സ്റ്റേറ്റ് (state), കൂടാതെ അതിന്റെ മുഴുവൻ DOM നോഡുകളുടെ സബ്‌ട്രീയും (subtree) മെമ്മറിയിൽ തന്നെ തങ്ങിനിൽക്കുന്നു. നൂറുകണക്കിന് ഇന്ററാക്ഷനുകളിലൂടെ, ഈ ഒബ്‌ജക്റ്റുകൾ കുന്നുകൂടുന്നു. മെമ്മറി ഉപയോഗം ഒരിക്കലും പൂർണ്ണമായി താഴെയാകാത്ത ഒരു സോ-ടൂത്ത് (sawtooth) പാറ്റേണിൽ വർദ്ധിച്ചുകൊണ്ടിരിക്കുന്നു.

എന്റർപ്രൈസ് ഡാഷ്‌ബോർഡ് പ്രശ്നം

ഉപയോക്താക്കൾ മണിക്കൂറുകളോളം ഒരു പേജിൽ തന്നെ ഇരിക്കുന്ന എന്റർപ്രൈസ് ഡാഷ്‌ബോർഡുകളിലാണ് ഇത് കൂടുതലായി സംഭവിക്കുന്നത്. മോണിറ്ററിംഗ് പാനലുകൾ, അനലിറ്റിക്സ് വ്യൂകൾ, അല്ലെങ്കിൽ ടിക്കറ്റിംഗ് സിസ്റ്റങ്ങൾ എന്നിവയെക്കുറിച്ച് ചിന്തിക്കുക. ഒരു ഉപയോക്താവ് ഒരു ഡീറ്റെയിൽ മോഡൽ തുറക്കുന്നു, വലിയൊരു ഡാറ്റാസെറ്റ് ഫിൽട്ടർ ചെയ്യുന്നു, അല്ലെങ്കിൽ ഒരു സിംഗിൾ-പേജ് ആപ്പിനുള്ളിൽ ടാബുകൾ മാറുന്നു. ഓരോ ഇന്ററാക്ഷനും ശരിയാണെന്ന് തോന്നാം. എന്നാൽ കാലക്രമേണ, അനാവശ്യമായ നോഡുകളും (orphaned nodes) ഡിറ്റാച്ച്ഡ് ലിസണറുകളും (detached listeners) കുന്നുകൂടുന്നു. ആപ്ലിക്കേഷൻ സാവധാനമാകുന്നത് ഒരു വലിയ റെൻഡറിംഗ് (render) മൂലമല്ല, മറിച്ച് ഇടയ്ക്കിടെയുള്ളതും ചിലവ് കൂടിയതുമായ ഗാർബേജ് കളക്ഷൻ പോസുകൾ (garbage collection pauses) ഉണ്ടാക്കാൻ പാകത്തിൽ ഹീപ്പ് (heap) വലുതാകുന്നത് കൊണ്ടാണ്.

നിങ്ങളുടെ useEffect ഹുക്കുകളെക്കുറിച്ച് ഊഹിച്ചുകൊണ്ടിരിക്കരുത്. ഒരു ലീക്ക് ഉണ്ടോ എന്ന് അറിയാനുള്ള ഏക വഴി ഹീപ്പ് നേരിട്ട് അളക്കുക എന്നതാണ്. Chrome DevTools നിങ്ങൾക്ക് ആ കാഴ്ച നൽകുന്നു.

Chrome DevTools ഉപയോഗിച്ച് ലീക്കുകൾ കണ്ടെത്താം

നിങ്ങൾക്ക് കൃത്യമായ ഒരു രീതിയും കുറച്ച് മിനിറ്റുകളുടെ ശ്രദ്ധയും ആവശ്യമാണ്. നിങ്ങളുടെ ആപ്ലിക്കേഷൻ Chrome-ൽ തുറക്കുക, DevTools ലോഞ്ച് ചെയ്യുക, തുടർന്ന് Memory ടാബിലേക്ക് പോകുക.

ഒരു ബേസ്‌ലൈൻ (baseline) റെക്കോർഡ് ചെയ്യുക. Heap snapshot തിരഞ്ഞെടുത്ത് Take snapshot ക്ലിക്ക് ചെയ്യുക. ഇത് നിലവിൽ JavaScript ഹീപ്പിലുള്ള എല്ലാ ഒബ്‌ജക്റ്റുകളെയും പിടിച്ചെടുക്കുകയും തുടക്കത്തിലെ മെമ്മറി കാണിക്കുകയും ചെയ്യുന്നു. പേജ് അതിന്റെ പ്രാരംഭ അവസ്ഥയിൽ (initial idle state) എത്തിയ ശേഷം മാത്രം ഇത് ചെയ്യുക, ലോഡിംഗ് സമയത്തല്ല, അങ്ങനെയാണെങ്കിൽ മാത്രമേ ഉപയോക്താവിന്റെ പ്രവർത്തനങ്ങൾ മൂലമുണ്ടാകുന്ന വളർച്ച മാത്രം നിങ്ങൾക്ക് അളക്കാൻ കഴിയൂ.

ആക്ഷൻ ട്രിഗർ ചെയ്യുക. ലീക്ക് ഉണ്ടാക്കുന്നു എന്ന് നിങ്ങൾ സംശയിക്കുന്ന അതേ UI ഇന്ററാക്ഷൻ ചെയ്യുക. ഒരു മോഡൽ തുറക്കുകയും അടയ്ക്കുകയും ചെയ്യുക, ഒരു കോംപ്ലക്സ് ചാർട്ട് ടോഗിൾ ചെയ്യുക, അല്ലെങ്കിൽ ഒരു റൂട്ട് മാറ്റി തിരികെ വരിക. കഴിഞ്ഞാൽ, ആപ്പിനെ അതിന്റെ യഥാർത്ഥ വിഷ്വൽ അവസ്ഥയിലേക്ക് തിരികെ കൊണ്ടുവരിക. ഈ ഘട്ടം വളരെ പ്രധാനമാണ്. ബേസ്‌ലൈൻ സമയത്ത് UI എങ്ങനെയാണോ അതുപോലെ തന്നെ ഇരിക്കണം നിങ്ങൾ ആഗ്രഹിക്കുന്നത്. UI ശൂന്യമായി കാണപ്പെടുന്നുണ്ടെങ്കിലും ഹീപ്പ് വർദ്ധിച്ചിട്ടുണ്ടെങ്കിൽ, അത് ഒരു ലീക്കിനുള്ള ശക്തമായ തെളിവാണ്.

ഗാർബേജ് കളക്ഷൻ നിർബന്ധമാക്കുക (Force garbage collection). Memory ടാബിലെ വേസ്റ്റ് ബിൻ (trash can) ഐക്കണിൽ ക്ലിക്ക് ചെയ്യുക. ഇത് ഒരു ഫുൾ GC സൈക്കിൾ ട്രിഗർ ചെയ്യുകയും അടുത്ത കളക്ഷൻ വരെ നിലനിൽക്കുന്ന താൽക്കാലിക ഒബ്‌ജക്റ്റുകളെ നീക്കം ചെയ്യുകയും ചെയ്യുന്നു. ബാക്കിയാകുന്നത് യഥാർത്ഥ ലീക്കുകളാണ്—കളക്ട് ചെയ്യേണ്ടതായിരുന്നുവെങ്കിലും അബദ്ധവശാൽ വന്ന റഫറൻസുകൾ കാരണം നിലനിൽക്കുന്ന ഒബ്‌ജക്റ്റുകൾ.

രണ്ടാമതൊരു സ്നാപ്‌ഷോട്ട് എടുക്കുക. വീണ്ടും Take snapshot ക്ലിക്ക് ചെയ്യുക. ഇപ്പോൾ ഒരേ UI സാഹചര്യങ്ങളിൽ എടുത്ത ഹീപ്പിന്റെ രണ്ട് ചിത്രങ്ങൾ നിങ്ങളുടെ പക്കലുണ്ട്.

ഫലങ്ങൾ താരതമ്യം ചെയ്യുക. വ്യൂ Summary-ൽ നിന്ന് Comparison എന്നതിലേക്ക് മാറ്റുക. ബേസ്‌ലൈനായി നിങ്ങളുടെ ആദ്യ സ്നാപ്‌ഷറ്റും, താരതമ്യം ചെയ്യേണ്ട സ്നാപ്‌ഷറ്റായി രണ്ടാമത്തേതും സെറ്റ് ചെയ്യുക. Comparison വ്യൂ എല്ലാ ഒബ്‌ജക്റ്റ് കാറ്റഗറികളും ലിസ്റ്റ് ചെയ്യുകയും രണ്ട് സ്നാപ്‌ഷറ്റുകൾ തമ്മിലുള്ള വ്യത്യാസം (delta), അതായത് ഒബ്‌ജക്റ്റുകളുടെ എണ്ണത്തിലുണ്ടായ മാറ്റം കാണിക്കുകയും ചെയ്യുന്നു.

Delta അനുസരിച്ച് ക്രമീകരിക്കുക (Sort by Delta). ഗണ്യമായി വർദ്ധിച്ച കാറ്റഗറികൾ ശ്രദ്ധിക്കുക. പ്രത്യേകിച്ച് Detached HTMLElement, React fiber nodes എന്നിവയിൽ ശ്രദ്ധ കേന്ദ്രീകരിക്കുക. ഒരു ഡിറ്റാച്ച്ഡ് HTML എലമെന്റ് എന്നത് നിലവിൽ ആക്റ്റീവ് ഡോക്യുമെന്റ് ട്രീയിൽ ഇല്ലാത്തതും എന്നാൽ ചില JavaScript റഫറൻസ് ഇപ്പോഴും അതിനെ പിടിച്ചിരിക്കുന്നതുമായ ഒരു DOM നോഡ് ആണ്. ഇവയാണ് പ്രധാന തെളിവുകൾ. ഒരു മോഡൽ അടച്ചുകഴിഞ്ഞോ അല്ലെങ്കിൽ ഒരു കമ്പോണന്റ് അൺമൗണ്ട് ചെയ്തുകഴിഞ്ഞോ ഇവ നിലനിൽക്കാൻ പാടില്ല.

Retaining Path വായിക്കുക

സ്നാപ്‌ഷറ്റിൽ ഒരു ഡിറ്റാച്ച്ഡ് എലമെന്റ് (detached element) തിരഞ്ഞെടുക്കുമ്പോൾ, ക്രോം (Chrome) താഴെയുള്ള പാനലിൽ റിറ്റൈനിംഗ് പാത്ത് (retaining path) കാണിക്കുന്നു. ഈ പാത്ത് റൂട്ട് മുതൽ തിരഞ്ഞെടുത്ത ഒബ്‌ജക്റ്റ് വരെയുള്ള റഫറൻസുകളുടെ ഒരു ശൃംഖലയാണ്. ഇത് ശ്രദ്ധാപൂർവ്വം പരിശോധിക്കുക. നിങ്ങളുടെ കംപോണന്റിലെ ഒരു പ്രത്യേക വരിയിലേക്ക് വിരൽ ചൂണ്ടുന്ന ഒരു ഇവന്റ് ലിസണർ (event listener), ഒരു IntersectionObserver, ഒരു setInterval ID, അല്ലെങ്കിൽ ഒരു ക്ലോഷർ (closure) എന്നിവ നിങ്ങൾ ഇതിൽ കാണാൻ സാധ്യതയുണ്ട്.

നിങ്ങൾക്ക് പരിചിതമായ പേരുകൾക്കായി തിരയുക. നിങ്ങളുടെ കോഡ്ബേസിലെ ഒരു ഫംഗ്ഷൻ പേരുമായി ബന്ധപ്പെട്ട് window-ൽ ഘടിപ്പിച്ചിട്ടുള്ള ഒരു ലിസണർ നിങ്ങൾ കണ്ടാൽ, നിങ്ങൾ ആ ആങ്കർ (anchor) കണ്ടെത്തിയിരിക്കുന്നു. ആ ലിസണർ നിലനിർത്തുന്ന ഒബ്‌ജക്റ്റ് നിങ്ങളുടെ കംപോണന്റിനെ മുഴുവനായി സജീവമായി നിലനിർത്തുന്നു. ചിലപ്പോൾ ഈ ശൃംഖല ഒരു തേർഡ് പാർട്ടി ലൈബ്രറിയിലൂടെ കടന്നുപോയേക്കാം. അത്തരം സാഹചര്യങ്ങളിൽ, ഒരു ക്ലീനപ്പ് ഫംഗ്ഷനിൽ (cleanup function) നിങ്ങൾ വിളിക്കാൻ മറന്നുപോയ ഒരു എക്സ്പ്ലിസിറ്റ് ടീർഡൗൺ കോൾ (explicit teardown call) ആ ലൈബ്രറി ആവശ്യപ്പെടുന്നുണ്ടോ എന്ന് പരിശോധിക്കുക.

മൂലകാരണങ്ങൾ പരിഹരിക്കുക

റിറ്റൈനിംഗ് പാത്ത് തിരിച്ചറിഞ്ഞുകഴിഞ്ഞാൽ, പരിഹാരം സാധാരണയായി സാങ്കേതികമായ ഒന്നായിരിക്കും, എന്നാൽ ടീമിലുടനീളം കൃത്യമായ അച്ചടക്കം ആവശ്യമാണ്.

ക്ലീനപ്പ് ഫംഗ്ഷനുകൾ ഉപയോഗിക്കുക. window അല്ലെങ്കിൽ document-ലേക്ക് ലിസണറുകൾ ചേർക്കുമ്പോൾ useEffect-ൽ എപ്പോഴും ഒരു ക്ലീനപ്പ് ഫംഗ്ഷൻ റിട്ടേൺ ചെയ്യുക. നിങ്ങളുടെ എഫക്റ്റ് ഒരു resize ഇവന്റിന് സബ്‌സ്‌ക്രൈബ് ചെയ്യുന്നുണ്ടെങ്കിൽ, കംപോണന്റ് അൺമൗണ്ട് (unmount) ചെയ്യുന്നതിന് മുമ്പ് ആ സബ്‌സ്‌ക്രിപ്ഷൻ നീക്കം ചെയ്യുക. React കംപോണന്റ് നീക്കം ചെയ്യുമ്പോൾ ക്ലീനപ്പ് പ്രവർത്തിക്കുന്നു, ഇത് ബാഹ്യ കണക്ഷനുകൾ വിച്ഛേദിക്കാൻ നിങ്ങൾക്ക് ഉറപ്പായ ഒരു മാർഗ്ഗം നൽകുന്നു.

റഫറൻസുകൾ സ്ഥിരപ്പെടുത്തുക. നിങ്ങളുടെ ഹാൻഡ്‌ലറുകൾ (handlers) useCallback-ൽ ഉൾപ്പെടുത്തുക. ഇത് നിങ്ങൾ ആദ്യം addEventListener-ലേക്ക് നൽകിയ അതേ ഫംഗ്ഷൻ റഫറൻസ് തന്നെ removeEventListener-ലേക്ക് നൽകുന്നുണ്ടെന്ന് ഉറപ്പാക്കുന്നു. നിങ്ങൾ window.addEventListener('resize', () => { ... }) എന്നതുപോലെ ഒരു ഇൻലൈൻ ഫംഗ്ഷൻ രജിസ്റ്റർ ചെയ്യുകയും പിന്നീട് മറ്റൊരു ഇൻലൈൻ ഫംഗ്ഷൻ ഉപയോഗിച്ച് അത് നീക്കം ചെയ്യാൻ ശ്രമിക്കുകയും ചെയ്താൽ, റഫറൻസുകൾ തമ്മിൽ യോജിക്കില്ല. ലിസണർ ഘടിപ്പിക്കപ്പെട്ട നിലയിൽ തന്നെ തുടരും. അതിനുള്ളിലെ ക്ലോഷർ നിങ്ങളുടെ കംപോണന്റ് സ്റ്റേറ്റിനെ (component state) സജീവമായി നിലനിർത്തുന്നു. ഒരു സ്റ്റേബിൾ डिपൻഡൻസി അറേയോടു കൂടിയ (stable dependency array) useCallback ഈ ഐഡന്റിറ്റി വ്യത്യാസം തടയുന്നു.

ഗ്ലോബൽ ലിങ്കുകൾ വിച്ഛേദിക്കുക. window, document തുടങ്ങിയ ബ്രൗസറിലെ ഇവന്റ് ടാർഗെറ്റ് ഒബ്‌ജക്റ്റുകൾ പേജിന്റെ ആയുസ്സ് മുഴുവൻ നിലനിൽക്കുന്നുണ്ടെന്ന് ഓർക്കുക. അവയിൽ നിന്നുള്ള കംപോണന്റിലേക്കുള്ള ഏതൊരു റഫറൻസും ഒരു ഗ്ലോബൽ ആങ്കറായി പ്രവർത്തിക്കുന്നു. ലിസണർ നീക്കം ചെയ്യുന്നത് ആ ആങ്കറിനെ തകർക്കുകയും അടുത്ത ഗാർബേജ് കളക്ഷൻ സൈക്കിൾ (garbage collection cycle) സമയത്ത് കംപോണന്റ് സ്റ്റേറ്റും DOM നോഡുകളും നീക്കം ചെയ്യാൻ V8 എഞ്ചിനെ അനുവദിക്കുകയും ചെയ്യുന്നു.

വിൻഡോ വിഡ്ത്ത് (window width) ട്രാക്ക് ചെയ്യുന്ന ഒരു മോഡൽ (modal) സങ്കൽപ്പിക്കുക. ക്ലീനപ്പ് ഇല്ലാതെ, ഓരോ തവണ ഉപയോക്താവ് മോഡൽ തുറക്കുമ്പോഴും ഒരു പുതിയ ലിസണർ ഘടിപ്പിക്കപ്പെടുന്നു. പഴയവ ഒരിക്കലും വിച്ഛേദിക്കപ്പെടുന്നില്ല, കാരണം അവ ഉൾപ്പെടുന്ന കംപോണന്റ് ഇൻസ്റ്റൻസുകൾ ഇല്ലാതായിട്ടുണ്ടാകാം, എന്നാൽ ഫംഗ്ഷനുകൾ അജ്ഞാതമായി (anonymous) പോയിട്ടുണ്ടാകാം. useCallback-ൽ ഉൾപ്പെടുത്തിയ ഒരു നെയിംഡ് ഹാൻഡ്‌ലറും (named handler), removeEventListener വിളിക്കുന്ന ഒരു ക്ലീനപ്പ് ഫംഗ്ഷനും ഉപയോഗിക്കുന്നത് ഈ പ്രശ്നം കൃത്യമായി പരിഹരിക്കുന്നു.

പ്രായോഗികമായ പാഠം

React-ലെ മെമ്മറി ലീക്കുകൾ (memory leaks) വളരെ അപൂർവ്വമായിട്ടേ വ്യക്തമായ എറർ മെസ്സേജുകളിലൂടെ സ്വയം വെളിപ്പെടുത്താറുള്ളൂ. അവ തുറന്നിരിക്കുന്ന സമയം കൂടുന്നതിനനുസരിച്ച് ടാബ് കൂടുതൽ ഭാരമുള്ളതാകുന്നു (slow ആകുന്നു) എന്നതിലൂടെയാണ് അവയെ തിരിച്ചറിയുന്നത്. ഏത് ഹുക്ക് (hook) ആണ് കുറ്റക്കാരൻ എന്ന് ഊഹിച്ചുകൊണ്ട് സമയം കളയരുത്. Memory ടാബ് തുറക്കുക, ഗാർബേജ് കളക്ഷൻ നിർബന്ധമാക്കുക (force garbage collection), സ്നാപ്‌ഷോട്ടുകൾ താരതമ്യം ചെയ്യുക. ഹീപ്പ് പ്രൊഫൈലർ (heap profiler) വഴി കൃത്യമായ റിറ്റൈനിംഗ് പാത്ത് കണ്ടെത്തുക. തുടർന്ന് ക്ലീനപ്പ് ഫംഗ്ഷൻ എഴുതുക, callback റഫറൻസ് സ്ഥിരപ്പെടുത്തുക, ഗ്ലോബൽ ലിങ്ക് വിച്ഛേദിക്കുക. നിങ്ങളുടെ ഉപയോക്താക്കൾ ഈ പരിഹാരം നേരിട്ട് ശ്രദ്ധിക്കില്ലായിരിക്കാം, പക്ഷേ ഡാഷ്‌ബോർഡ് ദിവസം മുഴുവൻ സുഗമമായി പ്രവർത്തിക്കുന്നുണ്ടെന്ന് അവർക്ക് മനസ്സിലാകും.