Ждере меморију и надувава се — шта је то, узроци и како избећи

Аутор: IT Sectr Објављено: 2026-07-28 Време читања: 10 мин

Цурење меморије — један од најподмуклијих проблема у мобилном развоју. Меморија апликације непрестано расте док не достигне границу коју је поставио оперативни систем, након чега следи OutOfMemoryError или принудно завршавање. Према Square Engineering, око 40% Android апликација има барем једно цурење меморије које се може открити само профилисањем. Размотрићемо узроке и методе спречавања раста меморије.

Главне тачке

  • GC reachability — објекат се не брише ако постоји активна референца из коренског скупа
  • Статичке референце на Activity или Context — најчешћи узрок цурења у Android-у
  • LeakCanary — стандардни алат за аутоматско откривање цурења у Android-у
  • WeakReference — решење за референце које не треба да ометају сакупљање отпада
  • Lifecycle-aware компоненте аутоматски отказују претплате при уништењу приказа

Шта је цурење меморије и надувавање апликације?

Цурење меморије (memory leak) — ситуација када објекат који више није потребан апликацији наставља да се задржава у heap-у јер постоји активна референца из коренског скупа (GC Root). Сакупљач отпада сматра такав објекат живим и не брише га.

Надувавање меморије (memory bloat) — шири проблем када апликација троши више меморије него што је потребно за обављање текућих задатака. Узроци: прекомерно кеширање, дуплирање објеката, неоптималне структуре података и фрагментација heap-а.

У Android-у свакој апликацији је додељен ограничен heap (обично 64-512 MB у зависности од уређаја и верзије ОС-а). У iOS-у ограничење је мање строго, али систем шаље упозорење о меморији при приближавању граници.

КарактеристикаAndroidiOS
Ограничење heap-а64-512 MB (зависи од уређаја)Имплицитно (системско)
Сакупљање отпадаART (Concurrent, Compact)ARC (Automatic Reference Counting)
Механизам цурењаGC Root референцеRetain cycles (снажни циклуси референци)
РезултатOutOfMemoryErrorУпозорење о меморији → завршавање

Према Facebook Engineering Blog, цурења меморије су узрок ~15% crash извештаја у мобилним апликацијама. У Android-у се томе додају ANR због честих GC пауза при недостатку меморије.

Типични обрасци цурења меморије у Android и iOS

Статичка референца на Activity — класика Android цурења. Ако статичко поље или синглтон чува референцу на Activity, она неће бити сакупљена од GC чак ни након finish(), док је синглтон жив. Activity је тежак објекат који садржи View хијерархију, ресурсе и Context.

kotlin
object LeakHolder {
    var activityRef: Activity ?= null // цурење: статичка референца на Activity
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        LeakHolder.activityRef = this // ❌ MainActivity никада неће бити сакупљен од GC
    }
}

Анонимне класе и ламбде — имплицитно задржавају референцу на спољашњу класу. Ако се Runnable или Callback проследи спољашњем сервису, а Activity се уништи, објекат анонимне класе још увек виси у реду и не дозвољава Activity-ју да оде на сакупљање отпада.

  • Handler са кашњењем — ако је Activity уништен, а Handler.postDelayed још није извршен, Activity цури
  • Thread и AsyncTask — при ротацији екрана Activity се поново креира, а стари Thread наставља да држи референцу на старо Activity
  • Retrofit/Callback — анонимни Callback задржава референцу на презентер или фрагмент
  • Посматрачи (Observers) — LiveData или RxJava претплате без отказа при onDestroy

У iOS-у главни проблем су retain cycles: два објекта држе снажне референце један на другог, а ARC не може да нулира бројач референци ни за један. Типичан случај: closure који снажно хвата self, и self који држи референцу на closure.

Како открити цурења меморије?

LeakCanary — библиотека од Square за аутоматско откривање цурења у Android-у. Након уништења Activity или Fragment проверава да ли је објекат сакупљен од GC. Ако није — прави heap dump и приказује trace цурења.

kotlin
// LeakCanary 2.x — ауто-интеграција преко Application
class ExampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // LeakCanary се аутоматски инсталира у debug build-у
        // преко ContentProvider-а — подешавање без кода
    }
}

// Принудни позив провере
AppWatcher.objectWatcher.watch(watchedObject, "leak description")

Android Studio Profiler — уграђени алат за праћење меморије у реалном времену. Омогућава снимање heap dump-а, проналажење сумњивих објеката (Retained Size > 1 MB) и праћење GC root пута до сваког објекта.

За iOS користите Xcode Memory Graph Debugger. Он визуализује граф објеката у меморији, приказује retain cycles и омогућава тренутно откривање кружних референци. Такође је доступан Instruments > Allocations за дугорочно праћење.

Стратегије спречавања цурења

WeakReference — основни механизам за референце које не треба да ометају сакупљање отпада. Ако GC одлучи да обрише објекат, WeakReference враћа null. Користи се за повратне позиве, слушаоце и референце на UI компоненте из позадинских нити.

Lifecycle-aware компоненте — архитектонски приступ имплементиран у Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Претплате се аутоматски отказују при onDestroy, што елиминише главну класу цурења.

kotlin
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<List<User>>()
    val data: LiveData<List<User>> get() = _data

    fun loadData() {
        viewModelScope.launch {
            val result = repository.fetchData()
            _data.postValue(result)
            // корутина се аутоматски отказује при onCleared()
        }
    }
}

viewModelScope и lifecycleScope — уграђени CoroutineScope у Android-у који се отказују при одговарајућем догађају животног циклуса. Ово елиминише цурења кроз корутине — најчешћи сценарио у савременом Android развоју.

  • Немојте користити статичке референце на Context, Activity, View или Fragment
  • Откажите све RxJava претплате у disposeBag / CompositeDisposable при onDestroy
  • Користите [weak self] / [unowned self] у iOS замыкањима за спречавање retain cycles
  • Проверавајте Bitmap и велике објекте — треба их рециклирати или поништити

Алати за профилисање меморије

Memory Profiler in Android Studio — главни алат за праћење heap-а. Приказује живе алокације, снимке heap-а, број објеката по типовима. Омогућава снимање dump-а и анализу у MAT (Memory Analyzer Tool) за проналажење сумњивих објеката.

Eclipse MAT — десктоп анализатор heap dump-а. Након учитавања HPROF датотеке из Android Studio, MAT гради стабло доминатора, приказује retain size сваког објекта и нуди аутоматску анализу сумњивих цурења кроз Leak Suspects Report.

Xcode Memory Graph — визуелни дебагер retain cycles. При клику на дугме Memory Graph Debugger, Xcode зауставља апликацију, гради потпун граф објеката у меморији и истиче retain cycles црвеном бојом.

АлатПлатформаКарактеристика
LeakCanaryAndroidАутооткривање цурења након destroy
Memory ProfilerAndroid StudioHeap dump + живе алокације
Eclipse MATAndroidСтабло доминатора, Leak Suspects Report
Memory GraphiOS (Xcode)Визуализатор retain cycles

Према Google I/O 2023, апликације које користе LeakCanary у debug верзијама смањују број crash-ова везаних за меморију за 30-50% у прва 2 месеца након имплементације. Препоручује се додавање LeakCanary у фази онбординга пројекта.

Често постављана питања

Чиме се разликује цурење меморије од надувавања?

Цурење — објекти недоступни коду, али их GC не брише због активних референци. Надувавање — апликација држи у меморији објекте који су логички потребни, али у прекомерној количини (нпр. кеш од 50 MB уз радну апликацију од 80 MB). Надувавање се лечи архитектонски, цурење — кроз исправно управљање референцама.

Како LeakCanary проналази цурења?

LeakCanary користи ObjectWatcher — након onDestroy() Activity креира WeakReference на Activity и покреће GC. Ако након 5 секунди WeakReference није очишћен, LeakCanary прави heap dump, анализира најкраћи ланац референци од GC Root до објекта и приказује тачан стек цурења са називом датотеке и линијом кода.

Зашто Bitmap често изазива OutOfMemoryError?

Bitmap заузима меморију ван Java heap-а у нативној меморији (native heap). Величина једног Bitmap-а = ширина × висина × 4 бајта (ARGB_8888). Фотографија од 12 MP (4000×3000) заузима 48 MB. Android не може увек благовремено да ослободи нативну меморију, што при нагомилавању више Bitmap-ова доводи до OOM чак и при довољном Java heap-у.

Шта је retain cycle у iOS-у?

Retain cycle — ситуација у ARC када два објекта држе снажне референце један на другог и бројач референци никада не достиже нулу. Типичан пример: ViewController са снажном референцом на closure, а closure снажно хвата self. Решење: користити [weak self] или [unowned self] у замыкањима.

Која је максимална величина heap-а на Android-у?

Величина heap-а зависи од уређаја и верзије Android-а. За старе уређаје (API 15-24) — 64-128 MB. За модерне (API 25+) — 256-512 MB. Тачна вредност се може добити кроз ActivityManager.getMemoryClass(). За велике апликације (игре, едитори) постоји largeHeap=true у манифесту, што даје до 1 GB.

Резиме

  • Цурење меморије — објекат који GC не брише због активне референце из коренског скупа; надувавање — прекомерна потрошња меморије без очигледних цурења
  • Статичке референце на Activity, Context, View — број један међу узроцима цурења у Android-у; решење — WeakReference или Application Context
  • Анонимне класе и ламбде имплицитно држе референцу на спољашњу класу; неотказани колбекови — други најчешћи узрок
  • LeakCanary — стандард аутоматског откривања цурења у Android-у; интеграција траје 5 минута и смањује стопу crash-ова за 30-50%
  • lifecycleScope и viewModelScope аутоматски отказују корутине при destroy, елиминишући читаву класу цурења
  • Retain cycles у iOS-у се решавају кроз weak/unowned self у замыкањима и делегатима
  • Профилишите меморију бар једном по sprint-у — heap dump са MAT или Memory Graph треба да буде део code review-а

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође