Pagtagas ng memorya: ano ito, mga tipikal na sitwasyon at diagnosis

May-akda: IT Sectr Nai-publish: 2026-07-29 Oras ng pagbabasa: 10 min

Pagtagas ng memorya (memory leak) — sitwasyon kung saan hindi binibigyang-laya ng application ang memoryang ginagamit ng mga bagay na hindi na kailangan. Sa mobile development, ito ay partikular na kritikal: limitadong heap at kawalan ng swap ay humahantong sa OutOfMemoryError at pag-crash ng application. Ayon sa Purdue University (2022), 35% ng mga Android application sa Google Play ay naglalaman ng kahit isang pagtagas ng memorya. Tatalakayin natin ang mga tipikal na sitwasyon, diagnostic tool, at paraan ng pag-aayos.

Mga pangunahing punto

  • GC Root — entry point kung saan tinutukoy ng garbage collector ang mga buhay na bagay
  • Pagtagas ng Context — ang pagpapasa ng Activity Context sa singleton ay humahantong sa paghawak ng buong View hierarchy
  • Handler na may postDelayed — kung ang Activity ay nawasak, hindi ito pinapayagan ng Handler na pumunta sa GC
  • Heap dump — pangunahing paraan ng pagsusuri ng pagtagas sa pamamagitan ng MAT o Android Profiler
  • SoftReference — alternatibo sa WeakReference para sa mga cache na may awtomatikong paglilinis kapag kulang ang memorya

Ano ang pagtagas ng memorya sa mga mobile application?

Pagtagas ng memorya — sitwasyon kung saan ang nakalaang memorya ay hindi ibinabalik sa sistema matapos ang bagay ay hindi na kailangan ng programa. Itinuturing ng garbage collector na buhay ang naturang bagay dahil may aktibong chain ng referensya mula sa GC Root patungo dito.

Sa Java/Kotlin, awtomatikong gumagana ang garbage collector, ngunit hindi nito matukoy na ang isang bagay ay lohikal na hindi kailangan kung mayroong teknikal na referensya dito. Dapat na tahasang putulin ng developer ang mga hindi kinakailangang koneksyon. Sa Swift/Objective-C, awtomatikong binibilang ng ARC ang mga referensya, ngunit hinaharangan ng retain cycles ang pag-reset ng counter.

Ang pangunahing panganib ng pagtagas ay ang cumulative effect. Bawat pagtagas ay kumokonsumo ng maliit na halaga ng memorya, ngunit sa maraming paglipat sa pagitan ng mga screen (pag-ikot ng screen, pagbubukas/pagsasara ng Activity), ang mga pagtagas ay nag-iipon hanggang sa maubos ang limitasyon ng heap.

Paano naiiba ang pagtagas sa pamamaga?

Pagtagas — ang bagay ay hindi naa-access ng code, ngunit hindi tinanggal ng GC. Pamamaga — ang bagay ay lohikal na kailangan, ngunit nakaimbak sa labis na dami. Halimbawa ng pamamaga: cache ng mga larawan na 100 MB na may working set na 30 MB. Parehong problema ay humahantong sa OOM, ngunit magkaiba ang mga sanhi at paraan ng paggamot.

Paano gumagana ang garbage collector at bakit nangyayari ang pagtagas?

ART (Android Runtime) ay gumagamit ng generational garbage collection na may concurrent compaction. Ang memorya ay nahahati sa batang henerasyon (Young), luma (Old), at malalaking bagay (Large). Ang mga bagay na nakaligtas sa ilang GC cycle ay inililipat sa Old generation, kung saan mas madalang ang pagtitipon — pinapabilis nito ang mga normal na cycle.

Nagsisimula ang GC kapag ang heap ay umabot sa isang tiyak na threshold ng occupancy (karaniwan 75-85%). Sa panahon ng GC, lahat ng thread ng application ay pinipigilan (STW — Stop The World). Kung mas maraming buhay na bagay, mas mahaba ang pause. Ang pagtagas ay nagpapataas ng bilang ng mga buhay na bagay, na nagpapahaba ng mga GC pause.

Tinutukoy ng kolektor ang mga buhay na bagay sa pamamagitan ng pagtawid sa graph mula sa GC Roots: static field, stack variable ng mga aktibong thread, JNI referensya. Anumang bagay na maaabot sa pamamagitan ng referensya mula sa mga ugat na ito ay itinuturing na buhay — kahit na alam ng developer na hindi na ito kailangan.

kotlin
// Halimbawa: static collection bilang GC Root — permanenteng pagtagas
object GlobalHolder {
    val listeners = mutableListOf<WeakReference<Any>>()
}

class LeakingFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        GlobalHolder.listeners.add(WeakReference(this))
        // WeakReference ay hindi humaharang sa GC — tamang pag-uugali
    }
}

WeakReference ay lumulutas ng problema: binabalewala ng GC ang weak referensya kapag tinutukoy ang mga buhay na bagay. Kung weak referensya na lamang ang natitira sa isang bagay, ito ay titipunin sa susunod na GC cycle.

Mga tipikal na sitwasyon ng pagtagas sa Android at iOS

Activity Context — ang pinakamalawak na sitwasyon ng pagtagas sa Android. Kung ang singleton, static field, o long-lived service ay nag-iimbak ng referensya sa Activity Context, ang buong Activity kasama ang lahat ng View ay hindi maaaring kolektahin ng GC. Solusyon: gamitin ang Application Context para sa mga long-lived na bagay.

Handler at mga ipinadalang mensahe — Ang Handler.postDelayed(runnable, delay) ay naglalagay ng mensahe sa queue ng Main Looper. Kung ang Activity ay nawasak bago matapos ang delay, ang mensahe ay nasa queue pa rin at humahawak ng referensya sa pamamagitan ng Runnable → anonymous class → panlabas na klase (Activity).

kotlin
class SafeActivity : AppCompatActivity() {
    private val mainHandler = Handler(Looper.getMainLooper())
    private val callback = Runnable { /* update UI */ }

    override fun onResume() {
        super.onResume()
        mainHandler.postDelayed(callback, 5000)
    }

    override fun onPause() {
        mainHandler.removeCallbacks(callback) // sapilitan: linisin ang queue
        super.onPause()
    }
}

Inner Classes — ang non-static inner class ay may implicit na referensya sa instance ng panlabas na klase. Kung ang panlabas na klase ay Activity at ang inner class ay ipinasa sa labas (halimbawa, sa RecyclerView.Adapter), hindi maaaring kolektahin ang Activity.

  • TimerTask at ScheduledExecutorService — mga gawain na naka-iskedyul bago ang pagkasira ng Activity
  • BroadcastReceiver — hindi na-unregister sa onPause/onDestroy ay patuloy na humahawak ng Context
  • ViewModel na may referensya sa View — ViewViewModel ay nabubuhay nang mas mahaba kaysa Activity, ang referensya sa View ay humahantong sa pagtagas
  • Retrofit Call — kung ang Call ay hindi nakansela, ang sagot ay darating sa nawasak na Fragment

Mga diagnostic tool para sa pagtagas ng memorya

Android Studio Memory Profiler — ang built-in na tool para sa real-time na pagsubaybay ng heap. Nagpapakita ng graph ng ginagamit na memorya, bilang ng mga alokasyon at bagay ayon sa uri. Pinapayagan ang pag-record ng heap dump at pag-export sa HPROF format para sa pagsusuri sa MAT.

Eclipse MAT (Memory Analyzer Tool) — desktop analyzer ng heap dump. Awtomatikong gumagawa ng Leak Suspects Report na nagha-highlight ng mga bagay na may pinakamalaking retained size at nagmumungkahi ng posibleng GC root chain para sa bawat kahina-hinalang bagay.

Xcode Memory Graph Debugger — para sa iOS. Pinipigilan ang application at vi-visualize ang graph ng mga bagay. Ang retain cycles ay naka-highlight sa pula, maaaring mag-click sa anumang bagay upang makita ang retain count at mga referensya nito.

ToolMga kakayahanKompleksidad
Memory ProfilerReal-time graph, heap dump, pagsubaybay ng alokasyon ng bagayMababa
Eclipse MATDominator tree, Leak Suspects, OQL queryKatamtaman
LeakCanaryAwtomatikong pagtuklas, trace ng pagtagas sa notificationMinimal
Xcode Memory GraphBiswal na graph ng retain cycles, listahan ng buhay na bagayMababa

Ayon sa Uber Engineering Blog, ang pagpapatupad ng awtomatikong profiling ng memorya (LeakCanary + heap dump analysis) sa CI/CD pipeline ay nagbabawas ng bilang ng mga insidenteng nauugnay sa memorya sa produksyon ng 60% sa loob ng 3 buwan.

Mga paraan ng pag-aayos ng pagtagas

Pagpapalit ng Context — kung ang bagay ay nabubuhay nang mas mahaba kaysa Activity, gamitin ang applicationContext. Lahat ng long-lived na bagay (singleton, repository, database helpers) ay dapat tumanggap ng Application Context, hindi Activity Context. Exception: mga UI component na nangangailangan ng access sa tema o resource na partikular sa Activity.

Lifecycle-aware component — ang paggamit ng LifecycleObserver, DefaultLifecycleObserver o reactivex ay awtomatikong kinakansela ang mga subscription sa onDestroy. Ang Android Jetpack ay nagbibigay ng lifecycleScope at viewModelScope, na nililinis ng kaukulang event.

Static inner class — kung ang inner class ay hindi nangangailangan ng access sa field ng panlabas na klase, gawin itong static. Ang static inner class ay walang implicit na referensya sa panlabas na klase. Kung kailangan ang access, gamitin ang WeakReference para sa tahasang referensya.

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ Non-static inner class — implicit na referensya sa MyActivity
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ Static inner class — walang implicit na referensya
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

Sa iOS, gumamit ng capture lists: [weak self] sa mga closure na maaaring mabuhay nang mas mahaba kaysa sa lumikha. Para sa mga delegate, gumamit ng mahinang referensya (weak var delegate). Para sa mga closure na garantisadong tatawagin lamang habang nabubuhay ang self, maaaring gamitin ang [unowned self], ngunit mag-ingat — ang pag-access sa pinalayang bagay ay magdudulot ng crash.

Mga madalas itanong

Paano makahanap ng pagtagas nang walang espesyal na tool?

Sa Android, magsagawa ng ilang paglipat sa pagitan ng mga screen (Activity A → B → A → B) at suriin ang adb shell dumpsys meminfo package_name. Kung ang Total PSS ay patuloy na tumataas at hindi bumabalik sa orihinal na halaga — may pagtagas. Sa iOS katulad: gamitin ang Debug Memory Graph sa Xcode para sa biswal na pagsusuri.

Maaari bang magdulot ng pagtagas ang Kotlin coroutine?

Oo, kung ang CoroutineScope ay hindi nakansela kapag ang component ay nawasak. Ang coroutine na inilunsad sa GlobalScope ay patuloy na tumatakbo kahit pagkatapos ng finish() ng Activity. Solusyon: gamitin ang viewModelScope (kinakansela sa onCleared) o lifecycleScope (kinakansela sa onDestroy). Para sa sariling Scope, gumawa ng lifecycle-aware scopes sa pamamagitan ng LifecycleOwner.

Paano nakakaapekto ang Bitmap sa pagtagas?

Bitmap ay nag-iimbak ng pixel data sa native heap, hindi sa Java heap. Ibig sabihin, hindi nakikita ng Java GC ang aktwal na laki ng Bitmap. Kung ang Bitmap ay hindi tinawagan ng recycle() o ang referensya ay hindi ni-null, ang native memorya ay hindi mapapalaya. Gamitin ang BitmapFactory na may inSampleSize para mag-load ng pinaliit na kopya at Glide/Coil para sa awtomatikong pamamahala ng cache.

Ano ang pagtagas sa pamamagitan ng static field?

Static field — ito ay GC Root. Ito ay nabubuhay hangga't ang klase ay na-load (sa Android — hangga't nabubuhay ang Process). Kung ang static field ay tumutukoy sa Activity, Bitmap, View, o anumang mabigat na bagay, ang bagay na ito ay hindi kailanman kokolektahin ng GC. Static field — walang hanggang referensya. Solusyon: mag-imbak lamang ng WeakReference o i-null ang static field sa onDestroy.

Paano maiwasan ang pagtagas sa iOS gamit ang ARC?

ARC ay awtomatikong nagpapalaya ng mga bagay kapag ang bilang ng malakas na referensya ay bumaba sa zero. Retain cycle — ang tanging paraan ng pagtagas sa ARC. Palaging gumamit ng weak para sa parent→child referensya, kung saan ang child ay dapat mabuhay nang mas mahaba kaysa parent (mga delegate, data source). Para sa mga closure, gumamit ng capture list [weak self] at suriin ang self para sa nil sa loob ng closure.

Buod

  • Pagtagas ng memorya — bagay na hindi naa-access ng code, ngunit hindi tinanggal ng GC dahil may aktibong referensya mula sa GC Root
  • GC Roots ay kinabibilangan ng static field, stack variable, at JNI referensya; anumang bagay na maaabot mula sa kanila ay buhay
  • Pagtagas ng Context — pinakamalawak na problema sa Android: pagpapasa ng Activity Context sa singleton o static field
  • Handler at Inner Class — pangalawang pinakamadalas na dahilan: hindi nakanselang mensahe sa Looper queue na humahawak ng referensya sa Activity
  • LeakCanary — karaniwang tool para sa awtomatikong pagtuklas; gumagawa ng heap dump at nagpapakita ng eksaktong GC root chain
  • lifecycleScope at viewModelScope ay lumulutas ng problema ng pagtagas sa pamamagitan ng coroutine — awtomatikong pagkansela sa destroy
  • I-profile ang memorya sa CI/CD: LeakCanary sa debug + heap dump analysis sa test run ay dapat humarang sa merge kung may bagong pagtagas

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.

Pag-usapan ang proyekto

Basahin din