Pagtagas ng memorya (Memory Leak) — sitwasyon kung saan ang isang app ay nagpapanatili ng mga reference sa mga bagay na hindi na kailangan, na pumipigil sa garbage collector na palayain ang occupied memory. Ayon sa datos ng LeakCanary, kahit sa mahusay na pagkakasulat na mga app ay may 3–5 na pagtagas bawat 10,000 linya ng code. Bawat pagtagas ay unti-unting binabawasan ang available na memorya, na humahantong sa pagbagal at OutOfMemoryError.
Mga Pangunahing Punto
Pagtagas ng memorya (Memory Leak) — sitwasyon kung saan ang isang bagay ay nananatiling maaabot sa pamamagitan ng chain ng malalalakas na reference (Strong Reference), kahit na lohikal na ito ay hindi na kailangan ng app. Itinuturing ng garbage collector (GC) na buhay ang naturang bagay at hindi pinapalaya ang memoryang inookupahan nito. Bilang resulta, ang available na heap memory ay patuloy na bumababa at ang dalas ng mga paghinto ng GC ay tumataas.
Hindi tulad ng mga wikang may manual memory management (C, C++), sa Java/Kotlin ang pagtagas ay hindi isang nakalimutang free(), kundi isang nakalimutang reference. Hangga't mayroong strong reference mula sa root object (GC Root) hanggang sa tumatagas na bagay, itinuturing ito ng GC na kailangan. Karaniwang GC Root: static field, aktibong thread, stack ng tawag, JNI-global na reference.
Panganib ng pagtagas ay ang pinagsama-samang epekto nito. Isang pagtagas na 100 KB ay hindi napapansin, ngunit 100 tulad ng pagtagas ay kumukuha ng 10 MB at ang app ay nagsisimulang bumagal dahil sa madalas na GC. Ang kritikal na masa ng pagtagas ay humahantong sa OutOfMemoryError at pag-crash ng app. Sintomas ng pagtagas: patuloy na pagtaas ng konsumo ng memorya sa graph ng Profiler, madalas na paghinto ng GC na may STW (Stop The World) at pagbaba ng performance ng UI.
Limang uri ng pagtagas ay sumasaklaw sa 95% ng mga kaso sa mobile development. Bawat isa ay may sariling sanhi at katangian na pattern sa code.
Pinaka kilalang pagtagas sa Android — pag-iimbak ng static na reference sa Activity o Context. Karaniwang code: isang static na field ng Activity na hindi nireretrieve sa onDestroy(). Habang buhay ang static na field, nananatiling buhay ang buong Activity kasama ang View tree nito na maaaring kumuha ng 1–10 MB. Ito ay klasikong pagtagas na unang natatagpuan ng LeakCanary.
Solusyon: huwag kailanman mag-imbak ng Activity o Context sa mga static na field. Gamitin ang Application Context para sa mga singleton na mas mahaba ang buhay kaysa Activity. Kung kailangan ng reference sa Activity — gumamit ng WeakReference<Activity>.
object MySingleton {
private var weakActivity: WeakReference<Activity>? = null
fun attach(activity: Activity) {
weakActivity = WeakReference(activity)
}
}
Anonymous na klase at non-static na nested na klase ay implicitly na nag-iimbak ng reference sa klase na naglalaman ng mga ito. Isang Runnable na ipinadala sa Handler na isinasagawa pagkatapos ng onDestroy() ay humahawak sa buong Activity. Isang Retrofit callback na nagsasara ng Activity ay gumagawa ng pareho. Ito ang pinakamapanlinlang na uri ng pagtagas — ang implicit na reference ay hindi nakikita sa code.
Ang Kotlin object expression at lambda ay kumukuha rin ng mga reference sa panlabas na klase. Gawing static (o top-level sa Kotlin) ang mga nested na klase at ipasa ang mga panlabas na reference sa pamamagitan ng WeakReference. Para sa mga lambda, gamitin ang Lifecycle-aware approach na may viewLifecycleOwner.
Pag-subscribe sa mga system service nang hindi nag-a-unsubscribe — direktang pagtagas. SensorManager, LocationManager, NotificationListener na nirehistro sa onResume() nang hindi tinatawag ang unregister sa onPause() ay humahawak sa Activity. Katulad: RxJava Disposable na hindi idinagdag sa CompositeDisposable at coroutine na inilunsad sa pamamagitan ng GlobalScope.
Gumamit ng Lifecycle-aware na component: observe() na may LifecycleOwner ay awtomatikong nag-a-unsubscribe sa onDestroy(). Para sa RxJava — viewLifecycleOwner.lifecycle.addObserver na may DisposableObserver. Para sa coroutine — lifecycleScope.launch() ay nakatali sa lifecycle.
// awtomatikong pag-unsubscribe sa pamamagitan ng Lifecycle
viewModel.userData.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// coroutine na may lifecycleScope
lifecycleScope.launch {
viewModel.loadData().collect { render(it) }
}
Bitmap ay kumukuha ng malaking halaga ng heap memory: isang FullHD-bitmap — 1920 × 1080 × 4 byte = 8.3 MB. Kung ang Bitmap ay ginagawa para sa bawat elemento ng listahan at ang recycle() ay hindi tinatawag kapag nagtatago, mabilis na nauubos ang memorya. Sa lumang bersyon ng Android (bago ang 3.0), ang Bitmark ay naka-imbak sa native memory, ngunit sa modernong bersyon — sa Dalvik/ART heap, at ang GC ay maaari lamang itong palayain kung walang strong reference.
Gumamit ng Glide o Coil para sa pag-load ng mga larawan — ang mga library na ito ay awtomatikong namamahala ng caching at recycle. Kung direktang nagtatrabaho sa Bitmap, tawagan ang bitmap.recycle() para sa malalaking larawan na hindi na ipinapakita at gamitin ang inSampleSize para sa pag-load ng maliliit na kopya.
Fragment ay may dalawang lifecycle: ang Fragment mismo at ang View nito. Pagkatapos ng onDestroyView() ang View tree ay nawasak, ngunit ang Fragment mismo ay maaaring manatili sa memorya kung mayroong panlabas na reference. Karaniwang error — pag-iimbak ng reference sa Fragment sa adapter ng ViewPager o sa navigation graph na hindi nalilinis kapag nawasak.
Huwag kailanman mag-imbak ng reference sa Fragment sa mga field ng mahabang-buhay na bagay. Gamitin ang childFragmentManager para sa nested na fragment at observe() na may LifecycleOwner para sa paglilipat ng data sa pagitan nila. Nalutas ng ViewPager2 ang problemang ito sa antas ng API: ang FragmentTransactionAdapter ay wastong namamahala ng lifecycle.
Pagtuklas ng pagtagas ay nangangailangan ng pag-verify ng dalawang katotohanan: ang memorya ay hindi bumabalik pagkatapos ng expected lifetime at ang bilang ng mga bagay ng isang partikular na uri ay tumataas nang hindi bumababa. Ang proseso ng diagnostic ay may tatlong yugto.
Unang yugto — visual na pagsusuri sa pamamagitan ng Memory Profiler sa Android Studio. Buksan ang tab na Memory, gawin ang target na aksyon (buksan at isara ang screen), pindutin ang GC (Garbage Collection) at tingnan kung bumalik ang memorya sa orihinal na antas. Kung pagkatapos ng 3–4 na cycle ng pagbukas-pagsara ang memorya ay patuloy na tumataas — may pagtagas.
Ikalawang yugto — pagkuha ng Heap Dump. Sa Memory Profiler pindutin ang Dump Java Heap. Ang nakuhang .hprof file ay buksan sa Android Studio: makikita mo ang lahat ng bagay sa heap na may mga sukat at reference. Hanapin ang mga klase na ang bilang ay dapat zero pagkatapos isara ang screen. Halimbawa, MainActivity na may bilang na 2 pagkatapos isara — malinaw na pagtagas.
Ikatlong yugto — pagsusuri ng Retained Size at GC Root. Sa Android Studio suriin ang Retained Size: gaano karaming memorya ang mapapalaya kung tatanggalin mo ang bagay na ito. Ang landas mula sa GC Root hanggang sa bagay ay nagpapakita kung ano ang humahawak dito: Static field → HashMap → Activity — at makikita mo ang punto ng pagtagas. Ang Reference widget panel ay nagpapakita ng lahat ng may hawak ng bagay.
Apat na tool ay sumasaklaw sa paghahanap ng pagtagas mula sa awtomatikong pagtuklas hanggang sa malalim na pagsusuri ng Heap Dump.
| Tool | Paraan | Format ng Resulta |
|---|---|---|
| LeakCanary | Awtomatikong pagmonitor | Heap Dump + stack trace ng pagtagas |
| Android Memory Profiler | Manu-manong pagmonitor | Graph ng memorya + Heap Dump |
| MAT (Eclipse) | Malalim na pagsusuri | Ulat ng Dominator Tree + landas ng GC Root |
| Perfetto | System-wide tracing | Timeline + native memory |
LeakCanary — must-have para sa bawat proyekto sa Android. Awtomatikong natutukoy ang pagtagas pagkatapos ng lifecycle ng Activity/Fragment at ipinapakita ang eksaktong lokasyon ng pagtagas na may stack trace. Integrasyon: isang linya sa build.gradle. Ang LeakCanary 2.x ay hindi nangangailangan ng manual na initialisasyon — awtomatikong nagrerehistro ng Application Watcher.
Pag-iwas sa pagtagas ay isinama sa proseso ng pag-develop sa pamamagitan ng isang set ng mga patakaran at tool na sumusuri ng code sa bawat yugto.
Huwag kailanman mag-imbak ng reference sa Activity, Fragment o View sa static field, singleton o mahabang-buhay na bagay. Kung hindi maiiwasan ang reference — gumamit ng WeakReference o mag-imbak ng data sa pamamagitan ng ViewModel na nabubuhay nang eksakto hangga't kailangan at hindi direktang humahawak ng View.
ViewModel at LiveData mula sa Android Architecture Components ay nilulutas ang problema ng lifecycle sa antas ng arkitektura. Ang ViewModel ay nabubuhay sa pag-ikot ng screen at hindi naglalaman ng mga reference sa View. Ang LiveData ay awtomatikong nag-a-unsubscribe ng observer sa onDestroy(). Gamitin ang mga ito sa halip na manu-manong pag-subscribe sa mga system service.
Sa code review bigyang pansin ang: static field na may uri ng Context/View, anonymous na klase, lambda na nagsasara ng Activity, manual na subscription, RxJava disposable na walang composite, pag-imbak ng Fragment sa pamamagitan ng Bundle. Sa Kotlin, suriin din ang coroutine para sa launch na walang koneksyon sa lifecycle.
LeakCanary ay maaaring gumana bilang bahagi ng pipeline ng pagsubok: patakbuhin ang acceptance test na may LeakCanary at markahan ang build na failed kung may natagpuang pagtagas. Ito ay pumipigil sa pagpasok ng pagtagas sa produksyon. Dagdagan ang pagsusuri ng Android Lint na may patakaran na StaticFieldLeak — ito ay nakakahanap ng potensyal na pagtagas sa antas ng static analysis.
// LeakCanary sa mga pagsubok
class LeakTest {
@Test
fun activityShouldNotLeak() {
ActivityScenario.launch(MainActivity::class.java)
.close()
LeakAssertions.assertNoLeak() // fail kung may pagtagas
}
}
Mga Madalas Itanong
Pagtagas ay ang sanhi, at OutOfMemoryError ay ang resulta. Isang pagtagas ay hindi humahantong sa OOM, ngunit ang akumulasyon ng dose-dosenang pagtagas ay nauubos ang Heap. Ang OOM ay isang nakamamatay na exception, at ang pagtagas ay isang pattern na humahantong dito sa paglipas ng panahon.
Sa pamamagitan ng Android Memory Profiler: buksan at isara ang screen ng 5 beses, pagkatapos ng bawat pagsara ay tawagan ang GC. Kung ang memorya ay hindi bumabalik sa baseline — may pagtagas. Kumuha ng Heap Dump at hanapin sa listahan ang klase ng Activity na ang bilang ay higit sa 0 pagkatapos isara.
Bahagyang. Nilulutas ng Kotlin ang problema ng null-safety, ngunit hindi nito pinamamahalaan ang strong references. Ang coroutine na may lifecycleScope at viewModelScope ay pumipigil sa pagtagas mula sa background task, at ang sealed class at data class ay nagbabawas ng bilang ng mga estado na humahantong sa pagtagas. Ang pangunahing proteksyon — mga architectural pattern, hindi mga function ng wika.
LeakCanary minsan ay nagbibigay ng false positive: ang isang bagay ay maaaring pansamantalang hawak ng system (halimbawa, InputMethodManager ay humahawak ng huling View). Suriin nang manu-mano: kung ang Retained Size < 1 KB at ang GC Root ay isang system service, malamang na ito ay isang maling alarma.
Hindi. Ang pagtagas ay posible sa anumang platform na may GC: iOS (Swift/Objective-C), Flutter (Dart), web browser (JavaScript). Ang mekanismo ay pareho — strong reference mula sa GC Root. Sa iOS, awtomatikong pinamamahalaan ng ARC ang memorya, ngunit ang retain cycle sa pagitan ng mga bagay ay lumilikha ng parehong pagtagas.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din