Fuite mémoire — l'un des problèmes les plus insidieux du développement mobile. L'utilisation mémoire de l'application augmente régulièrement jusqu'à atteindre la limite fixée par l'OS, suivie d'une OutOfMemoryError ou d'une terminaison forcée. Selon Square Engineering, environ 40% des applications Android ont au moins une fuite mémoire qui ne peut être détectée que par profilage. Examinons les causes et les méthodes pour prévenir la croissance mémoire.
Points clés
Fuite mémoire — une situation où un objet qui n'est plus nécessaire à l'application continue d'être conservé dans le tas parce qu'une référence active depuis l'ensemble racine (GC Root) pointe encore vers lui. Le ramasse-miettes considère cet objet comme vivant et ne le supprime pas.
Gonflement mémoire — un problème plus large où l'application consomme plus de mémoire que nécessaire pour effectuer ses tâches courantes. Causes : mise en cache excessive, duplication d'objets, structures de données sous-optimales et fragmentation du tas.
Sous Android, chaque application dispose d'un tas limité (généralement 64–512 Mo selon l'appareil et la version de l'OS). Sous iOS, la limite est moins stricte, mais le système envoie un avertissement mémoire à l'approche de la limite.
| Caractéristique | Android | iOS |
|---|---|---|
| Limite du tas | 64–512 Mo (selon l'appareil) | Implicite (système) |
| Ramasse-miettes | ART (Concurrent, Compact) | ARC (Comptage automatique de références) |
| Mécanisme de fuite | Références GC Root | Cycles de rétention (cycles de références fortes) |
| Résultat | OutOfMemoryError | Avertissement mémoire → terminaison |
Selon le Facebook Engineering Blog, les fuites mémoire causent ~15% des rapports de crash dans les applications mobiles. Sous Android, s'ajoutent les ANR dus aux pauses fréquentes du GC en cas de mémoire insuffisante.
Référence statique à Activity — une fuite classique sous Android. Si un champ statique ou un singleton conserve une référence à une Activity, elle ne sera pas collectée par le GC même après finish() tant que le singleton est vivant. Une Activity est un objet lourd contenant une hiérarchie de vues, des ressources et un Context.
object LeakHolder {
var activityRef: Activity ?= null // leak: static reference to Activity
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle ?= null) {
super.onCreate(savedInstanceState)
LeakHolder.activityRef = this // ❌ MainActivity will never be GC'd
}
}
Classes anonymes et lambdas — retiennent implicitement une référence à la classe externe. Si un Runnable ou Callback est passé à un service externe et que l'Activity est détruite, l'objet de la classe anonyme reste dans la file et empêche l'Activity d'être collectée.
Sous iOS, le problème principal est les cycles de rétention : deux objets maintiennent des références fortes l'un vers l'autre, et l'ARC ne peut mettre à zéro le compteur de références d'aucun des deux. Un cas typique : une closure qui capture self fortement, et self qui conserve une référence à la closure.
LeakCanary — une bibliothèque de Square pour la détection automatique des fuites sous Android. Après la destruction d'une Activity ou d'un Fragment, elle vérifie si l'objet a été collecté par le GC. Sinon, elle fait un heap dump et affiche la trace de la fuite.
// LeakCanary 2.x — auto-integration via Application
class ExampleApplication : Application() {
override fun onCreate() {
super.onCreate()
// LeakCanary auto-installs in debug build
// via ContentProvider — zero code setup
}
}
// Force check invocation
AppWatcher.objectWatcher.watch(watchedObject, "leak description")
Android Studio Profiler — un outil intégré de surveillance mémoire en temps réel. Il permet d'enregistrer un heap dump, de trouver des objets suspects (Retained Size > 1 Mo) et de tracer le chemin racine GC jusqu'à chaque objet.
Pour iOS, utilisez le Xcode Memory Graph Debugger. Il visualise le graphe d'objets en mémoire, montre les cycles de rétention et permet de détecter instantanément les références circulaires. Instruments > Allocations est également disponible pour la surveillance à long terme.
WeakReference — un mécanisme de base pour les références qui ne doivent pas interférer avec le ramasse-miettes. Si le GC décide de collecter un objet, WeakReference retourne null. Il est utilisé pour les callbacks, les écouteurs et les références aux composants d'interface depuis des threads d'arrière-plan.
Composants Lifecycle-aware — une approche architecturale implémentée dans Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Les abonnements sont automatiquement annulés à onDestroy, éliminant la principale classe de fuites.
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)
// coroutine auto-cancels on onCleared()
}
}
}
viewModelScope et lifecycleScope — des CoroutineScope intégrés dans Android qui sont annulés à l'événement de cycle de vie correspondant. Cela élimine les fuites via les coroutines — le scénario le plus courant dans le développement Android moderne.
Memory Profiler dans Android Studio — l'outil principal de surveillance du tas. Il montre les allocations en direct, les instantanés du tas et les comptes d'objets par type. Il permet d'enregistrer un dump et de l'analyser dans MAT (Memory Analyzer Tool) pour trouver des objets suspects.
Eclipse MAT — un analyseur de heap dump de bureau. Après avoir chargé un fichier HPROF depuis Android Studio, MAT construit un arbre dominant, montre la taille de rétention de chaque objet et propose une analyse automatique des fuites suspectes via le Leak Suspects Report.
Xcode Memory Graph — un débogueur visuel de cycles de rétention. En cliquant sur le bouton Memory Graph Debugger, Xcode arrête l'application, construit un graphe complet d'objets en mémoire et surligne les cycles de rétention en rouge.
| Outil | Plateforme | Fonctionnalité |
|---|---|---|
| LeakCanary | Android | Détection automatique des fuites après destroy |
| Memory Profiler | Android Studio | Heap dump + allocations en direct |
| Eclipse MAT | Android | Arbre dominant, Leak Suspects Report |
| Memory Graph | iOS (Xcode) | Visualiseur de cycles de rétention |
Selon Google I/O 2023, les applications utilisant LeakCanary dans les builds de débogage réduisent les crashes liés à la mémoire de 30–50% dans les 2 premiers mois suivant l'adoption. Il est recommandé d'ajouter LeakCanary dès la phase d'intégration du projet.
Questions fréquentes
Fuite — objets inaccessibles au code mais non collectés par le GC en raison de références actives. Gonflement — l'application conserve des objets logiquement nécessaires mais en quantité excessive (par exemple, un cache de 50 Mo dans une application de 80 Mo en cours d'exécution). Le gonflement se résout architecturalement, la fuite par une gestion correcte des références.
LeakCanary utilise ObjectWatcher — après onDestroy() d'une Activity, il crée une WeakReference sur l'Activity et exécute le GC. Si la WeakReference n'est pas effacée après 5 secondes, LeakCanary fait un heap dump, analyse la chaîne de référence la plus courte du GC Root à l'objet et affiche la pile exacte de la fuite avec le fichier et la ligne de code.
Bitmap occupe de la mémoire en dehors du tas Java dans la mémoire native (tas natif). La taille d'un Bitmap = largeur × hauteur × 4 octets (ARGB_8888). Une photo de 12 MP (4000×3000) prend 48 Mo. Android ne peut pas toujours libérer la mémoire native à temps, donc l'accumulation de plusieurs Bitmaps entraîne OOM même avec un tas Java suffisant.
Cycle de rétention — une situation dans l'ARC où deux objets maintiennent des références fortes l'un vers l'autre et le compteur de références n'atteint jamais zéro. Un exemple typique : un ViewController avec une référence forte à une closure, et la closure capture self fortement. Solution : utilisez [weak self] ou [unowned self] dans les closures.
La taille du tas dépend de l'appareil et de la version d'Android. Pour les anciens appareils (API 15–24) — 64–128 Mo. Pour les modernes (API 25+) — 256–512 Mo. La valeur exacte peut être obtenue via ActivityManager.getMemoryClass(). Pour les grandes applications (jeux, éditeurs), largeHeap=true dans le manifeste fournit jusqu'à 1 Go.
Résumé
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.