Tout ce que vous devez savoir sur les fuites de mémoire dans Android.
L’un des principaux avantages de Java, ou pour être plus précis, de la JVM (Java Virtual Machine), est le garbage collector (GC). Nous pouvons créer de nouveaux objets sans nous soucier de les libérer de la mémoire. Le ramasseur d’ordures se chargera d’allouer et de libérer la mémoire pour nous.
Pas exactement ! Nous pouvons empêcher le garbage collector de libérer la mémoire pour nous si nous ne comprenons pas complètement le fonctionnement du GC.
Écrire un code sans une bonne compréhension du fonctionnement du GC pourrait faire des fuites de mémoire dans l’app. Ces fuites peuvent affecter notre app en gaspillant la mémoire non libérée et provoque éventuellement des exceptions hors mémoire et des décalages.
- Qu’est-ce qu’une fuite de mémoire ?
- Attendez une minute!!🥴
- Heap &Pile
- Plus d’informations sur la mémoire Heap
- Vous êtes-vous déjà demandé quelle est la taille du tas pour votre application ?
- Comment pouvez-vous vérifier la taille de tas d’applications pour votre appareil?
- Comment cela fonctionne dans le monde réel ?
- Que se passe-t-il quand les méthodes se terminent ?
- Conclusion
- Qu’en est-il du tas ?
- Comment fonctionne le garbage collector?
- Que se passe-t-il lorsque le ramasseur d’ordures s’exécute ?
- Quand et comment se produisent les fuites de mémoire ?
- Comment pouvons-nous provoquer une fuite ?
- Comment pouvons-nous provoquer une fuite en utilisant un thread ?
- Flux régulier
Qu’est-ce qu’une fuite de mémoire ?
L’échec de la libération des objets inutilisés de la mémoire
L’échec de la libération des objets inutilisés de la mémoire signifie qu’il y a des objets inutilisés dans l’application que le GC ne peut pas effacer de la mémoire.
Lorsque le GC ne peut pas effacer les objets inutilisés de la mémoire, nous avons des problèmes. L’unité de mémoire qui contient les objets inutilisés sera occupée jusqu’à la fin de l’application ou (jusqu’à la fin de la méthode).
Jusqu’à la fin de la méthode ? Oui, c’est ça. Nous avons deux types de fuites, les fuites qui occupent l’unité de mémoire jusqu’à la fin de l’application et les fuites qui occupent l’unité de mémoire jusqu’à la fin de la méthode. La première est claire. La seconde nécessite plus de clarification. Prenons un exemple pour l’expliquer ! Supposons que nous ayons une méthode X. La méthode X effectue une tâche longue en arrière-plan, et il lui faudra une minute pour la terminer. De plus, la méthode X conserve des objets inutilisés pendant cette tâche. Dans ce cas, l’unité de mémoire sera occupée, et les objets inutilisés ne pourront pas être effacés pendant une minute jusqu’à la fin de la tâche. Après la fin de la méthode, le GC peut effacer les objets inutilisés et récupérer la mémoire.
C’est ce que je veux que vous sachiez pour l’instant nous y reviendrons plus tard avec du code et de la visualisation. CE SERA AMUSANT. 👹😜
Attendez une minute!!🥴
Avant de sauter au contexte, commençons par les bases.
La RAM, ou mémoire à accès aléatoire, est la mémoire des appareils androïdes ou des ordinateurs utilisée pour stocker les applications en cours d’exécution et leurs données.
Je vais expliquer deux personnages principaux de la RAM, le premier est le Heap, et le second est la Pile. Passons à la partie amusante 🤩🍻.
Heap &Pile
Je ne vais pas être trop long. Allons droit au but, une courte description, le Stack est utilisé pour l’allocation de mémoire statique tandis que le Heap est utilisé pour l’allocation de mémoire dynamique. Gardez juste à l’esprit que le Heap et le Stack sont tous deux stockés dans la RAM.
Plus d’informations sur la mémoire Heap
La mémoire Heap de Java est utilisée par la machine virtuelle pour allouer des objets. Chaque fois que vous créez un objet, il est toujours créé dans le tas. Les machines virtuelles, comme JVM ou DVM, effectuent une collecte régulière des déchets (GC), rendant la mémoire du tas de tous les objets qui ne sont plus référencés disponible pour de futures allocations.
Pour offrir une expérience utilisateur fluide, Android fixe une limite stricte à la taille du tas pour chaque application en cours d’exécution. La limite de la taille du tas varie selon les appareils et est basée sur la quantité de RAM dont dispose un appareil. Si votre application atteint cette limite de tas et tente d’allouer plus de mémoire, elle recevra un OutOfMemoryError
et se terminera.
Vous êtes-vous déjà demandé quelle est la taille du tas pour votre application ?
Découvrons cela ensemble. Dans android, nous avons la VM Dalvik (DVM). La DVM est une machine virtuelle Java unique, optimisée pour les appareils mobiles. Elle optimise la machine virtuelle pour la mémoire, l’autonomie de la batterie et les performances, et elle est responsable de la distribution de la quantité de mémoire pour chaque application.
Parlons de deux lignes dans la DVM:
- dalvik.vm.heapgrowthlimit : Cette ligne est basée sur la façon dont Dalvik va commencer dans la taille du tas de votre application. Il s’agit de la taille de tas par défaut pour chaque application. Le maximum que votre application peut atteindre!
- dalvik.vm.heapsize : Cette ligne représente la taille maximale du tas pour un tas plus grand. Vous pouvez y parvenir en demandant à android un plus grand tas dans votre manifeste d’application (android:largeHeap= »true »).
N’utilisez pas un plus grand tas dans votre application. Faites-le UNIQUEMENT si vous connaissez exactement l’effet secondaire de cette étape. Ici, je vais vous donner suffisamment d’informations pour continuer à faire des recherches sur le sujet.
Voici un tableau montrant quelle taille de tas vous avez obtenu en fonction de la RAM de votre appareil:
+==========================+=========+=========+===================+
| DVM | 1GB RAM | 2GB RAM | 3GB RAM OR HIGHER |
+==========================+=========+=========+===================+
| DEFAULT(heapgrowthlimit) | 64m | 128m | 256m |
+--------------------------+---------+---------+-------------------+
| LARGE(heapsize) | 128m | 256m | 512m |
+--------------------------+---------+---------+-------------------+
Souvenez-vous que plus vous avez de ram, plus la taille de tas sera élevée. Gardez à l’esprit que tous les appareils avec une ram plus élevée ne vont pas au-delà de 512m faites vos recherches sur votre appareil si votre appareil a plus de 3GB pour voir si votre taille de tas est plus grande que 512m.
Comment pouvez-vous vérifier la taille de tas d’applications pour votre appareil?
En utilisant le ActivityManager. Vous pouvez vérifier la taille maximale du tas au moment de l’exécution en utilisant les méthodes getMemoryClass()
ou getLargeMemoryClass()
(lorsqu’un grand tas est activé).
- getMemoryClass() : Renvoie la taille maximale du tas par défaut.
- getLargeMemoryClass() : Renvoie la taille maximale du tas disponible après avoir activé le drapeau de grand tas dans le manifeste.
ActivityManager am = getSystemService(ACTIVITY_SERVICE);
Log.d("XXX", "dalvik.vm.heapgrowthlimit: " + am.getMemoryClass());
Log.d("XXX", "dalvik.vm.heapsize: " + am.getLargeMemoryClass());
Comment cela fonctionne dans le monde réel ?
Nous utiliserons cette application simple pour comprendre quand nous utilisons le tas et quand nous utilisons la pile.
L’image ci-dessous montre une représentation du tas et de la pile de l’application et où chaque objet pointe et est stocké lorsque nous exécutons l’application.
Nous allons reprendre l’exécution de l’app, arrêter chaque ligne, expliquer quand l’application alloue les objets, et les stocke dans le tas ou la pile. Nous verrons également quand l’app libère les objets de la pile et du tas.
- Ligne 1 – La JVM crée un bloc de mémoire de pile pour la méthode main.
- Ligne 2 – Dans cette ligne, nous créons une variable locale primitive. La variable sera créée et stockée dans la mémoire de pile de la méthode principale.
- Ligne 3 -Ici je demande votre attention ! !! Dans cette ligne, nous créons un nouvel objet. L’objet est créé dans la pile de la méthode main et stocké dans le heap. La pile stocke la référence, l’adresse mémoire objet-heap (pointeur), tandis que le heap stocke l’objet original.
- Ligne 4 – La même chose que la ligne 3.
- Ligne 5 – La JVM crée un bloc de mémoire de pile pour la méthode foo.
- Ligne 6 -Créer un nouvel objet. L’objet est créé dans la mémoire de la pile de la méthode foo, et nous stockons dans la pile l’adresse en mémoire de tas de l’objet que nous avons créé à la ligne 3. La valeur (adresse en mémoire de tas de l’objet de la ligne 3) que nous avons transmise à la ligne 5. Gardez à l’esprit que Java passe toujours les références par valeur.
- Ligne 7 – Nous créons un nouvel objet. L’objet créé dans la pile et pointé vers le pool de chaînes dans le tas.
- Ligne 8 – Dans la dernière ligne de la méthode foo, la méthode s’est terminée. Et les objets seront libérés du bloc de pile de la méthode foo.
- Ligne 9- La même que la ligne 8, Dans la ligne finale de la méthode main, la méthode s’est terminée. Et le bloc de pile de la méthode principale devient libre.
Qu’en est-il de la libération de la mémoire du tas ? Nous allons y arriver bientôt. Prenez un café☕️, et continuez 😼.
Que se passe-t-il quand les méthodes se terminent ?
Chaque méthode a sa propre portée. Lorsque la méthode est terminée, les objets sont libérés et récupérés automatiquement de la pile.
L’utilisateur démarre l’application, ouvre l’activité ThreadActivity, et a attendu à l’écran jusqu’à la fin de la tâche de téléchargement. L’utilisateur a attendu pendant 20 secondes. Pourquoi 20 secondes ? Parce que c’est le temps que prend le thread pour terminer la tâche.
La tâche s’exécute en arrière-plan. L’utilisateur a attendu pendant 20 secondes l’achèvement de la tâche de téléchargement. Lorsque la tâche est terminée, la pile libère le bloc de méthode run().
Il n’y a pas de référence détenant la DownloadTask. Le GC a considéré l’objet DownladTask comme un objet inutilisé, et pour cette raison, le prochain cycle du GC l’effacera de la mémoire du tas.
Le GC efface les objets inutilisés du tas. Maintenant, lorsque l’utilisateur ferme l’activité. La méthode principale sera libérée de la pile, et dans le prochain cycle du GC, le GC effacera le ThreadActivity de la mémoire du tas.
Parfait!