Fuite de mémoire : ce que c'est, scénarios typiques et diagnostic

Auteur : IT Sectr Publié le : 2026-07-29 Temps de lecture : 10 min

Fuite de mémoire (memory leak) — situation dans laquelle une application ne libère pas la mémoire occupée par des objets qui ne sont plus nécessaires. Dans le développement mobile, c'est particulièrement critique : le heap limité et l'absence de swap entraînent une OutOfMemoryError et un plantage de l'application. Selon Purdue University (2022), 35% des applications Android sur Google Play contiennent au moins une fuite de mémoire. Analysons les scénarios typiques, les outils de diagnostic et les méthodes de correction.

Points clés

  • GC Root — point d'entrée par lequel le ramasse-miettes détermine les objets vivants
  • Fuite de Context — passer Activity Context à un singleton retient toute la hiérarchie des vues
  • Handler avec postDelayed — si l'Activity est détruite, le Handler l'empêche d'être collectée par le GC
  • Heap dump — méthode principale d'analyse des fuites via MAT ou Android Profiler
  • SoftReference — alternative à WeakReference pour les caches avec nettoyage automatique en cas de manque de mémoire

Qu'est-ce qu'une fuite de mémoire dans les applications mobiles ?

Fuite de mémoire — situation dans laquelle la mémoire allouée n'est pas restituée au système après que l'objet n'est plus nécessaire au programme. Le ramasse-miettes considère cet objet comme vivant car une chaîne de références active depuis une GC Root pointe vers lui.

En Java/Kotlin, le ramasse-miettes fonctionne automatiquement, mais il ne peut pas déterminer qu'un objet est logiquement inutile s'il existe une référence technique vers lui. Le développeur doit rompre explicitement les connexions inutiles. En Swift/Objective-C, ARC compte automatiquement les références, mais les cycles de rétention bloquent la diminution du compteur.

Le principal danger des fuites est l'effet cumulatif. Chaque fuite consomme une petite quantité de mémoire, mais avec des transitions répétées entre écrans (rotations d'écran, ouverture/fermeture d'Activities), les fuites s'accumulent jusqu'à épuiser la limite du heap.

En quoi une fuite diffère-t-elle du gonflement ?

Fuite — l'objet est inaccessible au code mais n'est pas supprimé par le GC. Gonflement — l'objet est logiquement nécessaire mais stocké en quantité excessive. Exemple de gonflement : un cache d'images de 100 Mo avec un ensemble de travail de 30 Mo. Les deux problèmes mènent à OOM, mais les causes et les méthodes de traitement diffèrent.

Comment fonctionne le ramasse-miettes et pourquoi les fuites se produisent-elles ?

ART (Android Runtime) utilise le ramasse-miettes générationnel avec compactage concurrent. La mémoire est divisée en génération jeune (Young), génération âgée (Old) et objets volumineux (Large). Les objets qui survivent à plusieurs cycles GC sont déplacés vers la génération Old, où la collecte a lieu moins fréquemment — cela accélère les cycles normaux.

Le GC démarre lorsque le heap atteint un certain seuil d'occupation (généralement 75-85%). Pendant le GC, tous les threads de l'application sont suspendus (STW — Stop The World). Plus il y a d'objets vivants, plus la pause est longue. Les fuites augmentent le nombre d'objets vivants, allongeant les pauses du GC.

Le collecteur détermine les objets vivants en parcourant le graphe à partir des GC Roots : champs statiques, variables de pile des threads actifs, références JNI. Tout objet atteignable via des références depuis ces racines est considéré comme vivant — même si le développeur sait qu'il n'est plus nécessaire.

kotlin
// Exemple : collection statique comme GC Root — fuite permanente
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 n'empêche pas le GC — comportement correct
    }
}

WeakReference résout le problème : le GC ignore les références faibles lors de la détermination des objets vivants. S'il ne reste que des références faibles vers un objet, il sera collecté lors du cycle GC le plus proche.

Scénarios typiques de fuites dans Android et iOS

Activity Context — le scénario de fuite le plus massif dans Android. Si un singleton, un champ statique ou un service de longue durée stocke une référence à un Activity Context, toute l'Activity avec toutes ses vues ne peut pas être collectée par le GC. Solution : utilisez Application Context pour les objets de longue durée.

Handler et messages envoyés — Handler.postDelayed(runnable, delay) place un message dans la file d'attente du Main Looper. Si l'Activity est détruite avant l'expiration du délai, le message est toujours dans la file et maintient une référence via Runnable → classe anonyme → classe externe (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) // obligatoire : vider la file d'attente
        super.onPause()
    }
}

Classes internes — une classe interne non statique a une référence implicite à l'instance de la classe externe. Si la classe externe est une Activity et que la classe interne est passée à l'extérieur (par exemple, à RecyclerView.Adapter), l'Activity ne peut pas être collectée.

  • TimerTask et ScheduledExecutorService — tâches planifiées avant la destruction de l'Activity
  • BroadcastReceiver — non désenregistré dans onPause/onDestroy continue de maintenir le Context
  • ViewModel avec référence à View — ViewModel survit à l'Activity, la référence à la View entraîne une fuite
  • Retrofit Call — si Call n'est pas annulé, la réponse arrive à un Fragment détruit

Outils de diagnostic des fuites de mémoire

Android Studio Memory Profiler — outil intégré de surveillance du heap en temps réel. Affiche un graphique de la mémoire utilisée, le nombre d'allocations et d'objets par type. Permet d'enregistrer un heap dump et de l'exporter au format HPROF pour analyse dans MAT.

Eclipse MAT (Memory Analyzer Tool) — analyseur de bureau de heap dump. Construit automatiquement des rapports Leak Suspects, mettant en évidence les objets avec le plus grand retained size et suggérant la chaîne GC Root suspectée pour chaque objet suspect.

Xcode Memory Graph Debugger — pour iOS. Suspend l'application et visualise le graphe d'objets. Les cycles de rétention sont surlignés en rouge ; vous pouvez cliquer sur n'importe quel objet pour voir son retain count et ses références.

OutilCapacitésComplexité
Memory ProfilerGraphique en temps réel, heap dump, Object Allocation TrackingFaible
Eclipse MATDominator tree, Leak Suspects, requêtes OQLMoyenne
LeakCanaryDétection automatique, trace de fuite dans la notificationMinimale
Xcode Memory GraphGraphe visuel des cycles de rétention, liste d'objets vivantsFaible

Selon le Uber Engineering Blog, l'intégration du profilage automatique de la mémoire (LeakCanary + analyse de heap dump) dans le pipeline CI/CD réduit les incidents liés à la mémoire en production de 60% en 3 mois.

Méthodes pour éliminer les fuites

Remplacer Context — si un objet survit à l'Activity, utilisez applicationContext. Tous les objets de longue durée (singletons, repositories, helpers de base de données) doivent recevoir Application Context, pas Activity Context. Exception : les composants d'interface utilisateur qui ont besoin d'accéder au thème ou aux ressources spécifiques à l'Activity.

Composants sensibles au cycle de vie — l'utilisation de LifecycleObserver, DefaultLifecycleObserver ou d'extensions réactives annule automatiquement les abonnements à onDestroy. Android Jetpack fournit lifecycleScope et viewModelScope, qui sont nettoyés par l'événement de cycle de vie correspondant.

Classe interne statique — si la classe interne n'a pas besoin d'accéder aux champs de la classe externe, rendez-la static. Une classe interne statique n'a pas de référence implicite à la classe externe. Si l'accès est nécessaire, utilisez WeakReference pour la référence explicite.

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ Classe interne non statique — référence implicite à MyActivity
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ Classe interne statique — pas de référence implicite
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

Sous iOS, utilisez des listes de capture : [weak self] dans les fermetures qui peuvent survivre à leur créateur. Pour les délégués, utilisez des références faibles (weak var delegate). Pour les fermetures dont l'appel est garanti uniquement pendant la durée de vie de self, [unowned self] peut être utilisé, mais avec prudence — l'accès à un objet désalloué provoquera un plantage.

Foire aux questions

Comment trouver une fuite sans outils spéciaux ?

Sous Android, effectuez plusieurs transitions entre écrans (Activity A → B → A → B) et vérifiez adb shell dumpsys meminfo package_name. Si le Total PSS augmente régulièrement et ne revient pas à la valeur d'origine — il y a une fuite. Sous iOS, de même : utilisez Debug Memory Graph dans Xcode pour une inspection visuelle.

Une coroutine Kotlin peut-elle provoquer une fuite ?

Oui, si le CoroutineScope n'est pas annulé lors de la destruction du composant. Une coroutine lancée dans GlobalScope continue de s'exécuter même après finish() de l'Activity. Solution : utilisez viewModelScope (annulé dans onCleared) ou lifecycleScope (annulé dans onDestroy). Pour les scopes personnalisés, créez des scopes sensibles au cycle de vie via LifecycleOwner.

Comment Bitmap affecte-t-il les fuites ?

Bitmap stocke les données de pixels dans le heap natif, pas dans le heap Java. Cela signifie que le GC Java ne voit pas la taille réelle du Bitmap. Si Bitmap n'est pas recyclé avec recycle() ou si la référence n'est pas mise à null, la mémoire native n'est pas libérée. Utilisez BitmapFactory avec inSampleSize pour charger des copies réduites et Glide/Coil pour la gestion automatique du cache.

Qu'est-ce qu'une fuite via un champ statique ?

Un champ statique est une GC Root. Il vit tant que la classe est chargée (sous Android — tant que le Process est vivant). Si un champ statique référence une Activity, Bitmap, View ou tout autre objet lourd, cet objet ne sera jamais collecté par le GC. Un champ statique est une référence éternelle. Solution : stockez uniquement WeakReference ou mettez le champ statique à null dans onDestroy.

Comment éviter les fuites sous iOS avec ARC ?

ARC libère automatiquement les objets lorsque le compteur de références fortes tombe à zéro. Un cycle de rétention est la seule façon de fuir sous ARC. Utilisez toujours weak pour les références parent→enfant où l'enfant peut survivre au parent (délégués, sources de données). Pour les fermetures, utilisez la liste de capture [weak self] et vérifiez que self n'est pas nil à l'intérieur de la fermeture.

Résumé

  • Fuite de mémoire — un objet est inaccessible au code mais n'est pas supprimé par le GC car une référence active depuis une GC Root existe
  • GC Roots incluent les champs statiques, les variables de pile et les références JNI ; tout objet atteignable depuis eux est vivant
  • Fuite de Context — le problème le plus répandu dans Android : passer Activity Context à un singleton ou un champ statique
  • Handler et classe interne — la deuxième cause la plus fréquente : les messages non annulés dans la file du Looper maintiennent une référence à l'Activity
  • LeakCanary — l'outil standard de détection automatique ; prend un heap dump et montre la chaîne exacte de GC Root
  • lifecycleScope et viewModelScope résolvent le problème des fuites par coroutines — annulation automatique à la destruction
  • Profilez la mémoire dans le CI/CD : LeakCanary en debug + analyse de heap dump dans les tests doivent bloquer le merge en cas de nouvelles fuites

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi