Résumé de la fuite de mémoire Android
Le but de la gestion de la mémoire est de nous aider à éviter efficacement les fuites de mémoire dans nos applications pendant le développement. Tout le monde connaît les fuites de mémoire. Pour le dire simplement, cela signifie que l'objet qui doit être libéré n'a pas été libéré et a été détenu par une certaine instance mais n'est plus utilisé, de sorte que le GC ne peut pas être recyclé. Récemment, j'ai lu de nombreux documents et documents pertinents. Je prévois de les résumer et de les régler et de partager et d'apprendre avec vous, et de me donner un avertissement sur la façon d'éviter ces situations lors du codage à l'avenir et d'améliorer l'expérience et la qualité de l'application.
Je vais commencer par les bases des fuites de mémoire Java et utiliser des exemples spécifiques pour illustrer les différentes causes des fuites de mémoire d'Android, ainsi que sur la façon d'utiliser des outils pour analyser les fuites de mémoire de l'application, et enfin les résumer.
Stratégie d'allocation de la mémoire Java
Il existe trois types de stratégies d'allocation de mémoire lorsque les programmes Java s'exécutent, à savoir l'allocation statique, l'allocation de pile et l'allocation de tas. En conséquence, l'espace mémoire utilisé par les trois stratégies de stockage est principalement une zone de stockage statique (également connue sous le nom de zone de méthode), la zone de pile et la zone de tas.
Zone de stockage statique (zone de méthode): stocke principalement des données statiques, des données et des constantes statiques globales. Ce morceau de mémoire est alloué lorsque le programme est compilé et existe tout au long de l'exécution du programme.
Zone de pile: Lorsqu'une méthode est exécutée, les variables locales dans le corps de la méthode (y compris le type de données de base et la référence d'objet) sont créées sur la pile, et la mémoire maintenue par ces variables locales sera automatiquement libérée à la fin de l'exécution de la méthode. Étant donné que l'opération d'allocation de mémoire de pile est intégrée à l'ensemble d'instructions du processeur, il est très efficace, mais la capacité de mémoire allouée est limitée.
Zone de tas: également connu sous le nom d'allocation de mémoire dynamique, se réfère généralement à la mémoire directement nouvelle lorsque le programme est en cours d'exécution, c'est-à-dire une instance de l'objet. Lorsque cette partie de la mémoire n'est pas utilisée, le collecteur de déchets Java sera responsable du recyclage.
La différence entre la pile et le tas:
Certains types de variables de base définis dans le corps de la méthode et les variables de référence des objets sont alloués dans la mémoire de pile de la méthode. Lorsqu'une variable est définie dans un bloc de méthode, Java alloue l'espace mémoire pour la variable de la pile. Lorsque la portée de la variable est dépassée, la variable sera invalide et l'espace mémoire qui lui est alloué sera libéré et l'espace mémoire peut être réutilisé.
La mémoire du tas est utilisée pour stocker tous les objets créés par les nouveaux (y compris toutes les variables membre dans l'objet) et les tableaux. La mémoire allouée dans le tas sera automatiquement gérée par le collectionneur Java Garbage. Une fois qu'un tableau ou un objet a été généré dans le tas, une variable spéciale peut être définie dans la pile. La valeur de cette variable est égale à la première adresse du tableau ou de l'objet dans la mémoire du tas. Cette variable spéciale est la variable de référence que nous avons mentionnée ci-dessus. Nous pouvons accéder aux objets ou à des tableaux dans le tas via cette variable de référence.
Par exemple:
classe publique échantillon {int s1 = 0; échantillon msample1 = new échantillon (); Méthode publique void () {int s2 = 1; échantillon msample2 = new échantillon ();}} exemple msample3 = new échantillon (); La variable locale S2 de la classe d'échantillon et la variable de référence MSample2 existent toutes deux sur la pile, mais l'objet pointé par Msample2 existe sur le tas.
L'entité d'objet pointé par MSample3 est stocké sur le tas, y compris toutes les variables de membres S1 et MSample1 de cet objet, et elle s'existe dans la pile.
en conclusion:
Les types de données de base et les références des variables locales sont stockés dans la pile et les entités d'objet référencées sont stockées dans le tas. - Parce qu'ils appartiennent à des variables dans les méthodes, le cycle de vie se termine par des méthodes.
Les variables des membres sont toutes stockées et dans le tas (y compris les types de données de base, les entités d'objets référencées et référencées) - Parce qu'elles appartiennent à des classes, les objets de classe seront finalement utilisés pour une nouvelle utilisation.
Après avoir compris l'allocation de la mémoire de Java, regardons comment Java gère la mémoire.
Comment Java gère la mémoire
La gestion de la mémoire de Java est la question de l'allocation et de la libération d'objets. En Java, les programmeurs doivent demander un espace mémoire pour chaque objet via le mot-clé nouveau (sauf pour les types de base), et tous les objets allacent l'espace dans le tas (tas). De plus, la libération d'objets est déterminée et exécutée par le GC. En Java, l'allocation de mémoire est effectuée par les programmes, tandis que la version de la mémoire est effectuée par GC. Cette méthode de revenus et de dépenses sur deux lignes simplifie le travail des programmeurs. Mais en même temps, cela ajoute également au travail du JVM. C'est également l'une des raisons pour lesquelles les programmes Java sont plus lents. Étant donné que, afin de libérer correctement les objets, GC doit surveiller l'état exécuté de chaque objet, y compris l'application, la citation, la citation, l'attribution, etc. de l'objet, et GC doit le surveiller.
La surveillance de l'état d'un objet est de libérer l'objet plus précisément et en temps opportun, et le principe fondamental de la libération de l'objet est que l'objet n'est plus référencé.
Pour mieux comprendre le fonctionnement de GC, nous pouvons considérer l'objet comme un sommet d'un graphique dirigé, et la relation de référence comme les bords directs du graphique, qui pointent du référencier à l'objet référencé. De plus, chaque objet de thread peut être utilisé comme sommet de départ d'un graphique. Par exemple, la plupart des programmes commencent par le processus principal, donc le graphique est un arbre racine commençant par le sommet principal du processus. Dans ce graphique dirigé, les objets accessibles par le sommet racine sont des objets valides et GC ne recyclera pas ces objets. Si un objet (sous-graphe connecté) est inaccessible à partir de ce sommet racine (notez que le graphique est un graphique dirigé), alors nous pensons que cet objet (ces) n'est plus référencé et peut être recyclé par GC.
Ci-dessous, nous donnons un exemple de la façon d'utiliser des graphiques dirigés pour représenter la gestion de la mémoire. Pour chaque instant du programme, nous avons un graphique dirigé représentant l'allocation de mémoire du JVM. L'image ci-dessous est un diagramme du programme à gauche à la ligne 6.
Java utilise des graphiques dirigés pour la gestion de la mémoire, qui peuvent éliminer le problème des boucles de référence. Par exemple, il existe trois objets qui se réfèrent les uns aux autres. Tant qu'ils et le processus racine sont inaccessibles, GC peut également les recycler. L'avantage de cette méthode est qu'il a une grande précision dans la gestion de la mémoire, mais est une faible efficacité. Une autre technologie de gestion de la mémoire couramment utilisée consiste à utiliser des compteurs. Par exemple, le modèle COM utilise la méthode du compteur pour gérer les composants. Par rapport aux graphiques dirigés, il a des lignes de précision faibles (il est difficile de faire face aux problèmes de référence circulaires), mais il a une efficacité d'exécution élevée.
Qu'est-ce qu'une fuite de mémoire en java
En Java, les fuites de mémoire sont l'existence de certains objets alloués, qui ont les deux caractéristiques suivantes. Premièrement, ces objets sont accessibles, c'est-à-dire que dans le graphique dirigé, il existe des chemins qui peuvent y être connectés; Deuxièmement, ces objets sont inutiles, c'est-à-dire que le programme n'utilisera plus ces objets à l'avenir. Si l'objet remplit ces deux conditions, ces objets peuvent être déterminés comme une fuite de mémoire en Java, et ces objets ne seront pas recyclés par GC, mais il occupe la mémoire.
En C ++, les fuites de mémoire ont une plus grande portée. Certains objets reçoivent un espace mémoire alloué, mais alors inaccessible. Puisqu'il n'y a pas de GC en C ++, ces souvenirs ne seront jamais collectés. En Java, ces objets inaccessibles sont recyclés par GC, donc les programmeurs n'ont pas besoin de considérer cette partie de la fuite de mémoire.
Grâce à l'analyse, nous savons que pour C ++, les programmeurs doivent gérer les bords et les sommets par eux-mêmes, tandis que pour les programmeurs Java, ils n'ont qu'à gérer les bords (pas besoin de gérer la version des sommets). De cette façon, Java améliore l'efficacité de la programmation.
Par conséquent, grâce à l'analyse ci-dessus, nous savons qu'il y a aussi des fuites de mémoire en Java, mais la portée est plus petite que celle de C ++. Parce que Java Language garantit que tout objet est accessible, tous les objets inaccessibles sont gérés par GC.
Pour les programmeurs, GC est fondamentalement transparent et invisible. Bien que nous n'ayons que quelques fonctions pour accéder à GC, tels que System.gc (), qui exécute GC, selon la définition de la spécification du langage Java, cette fonction ne garantit pas que le collecteur d'ordures de JVM s'exécutera. Parce que différents implémenteurs JVM peuvent utiliser différents algorithmes pour gérer GC. Généralement, les fils de GC ont une priorité plus faible. Il existe de nombreuses stratégies pour que JVM appelle GC. Certains d'entre eux ne commencent à fonctionner que lorsque l'utilisation de la mémoire atteint un certain niveau. Certains les exécutent régulièrement. Certains exécutent GC en douceur et certains exécutent GC de manière interrompue. Mais de manière générale, nous n'avons pas besoin de nous soucier de cela. Sauf dans certaines situations spécifiques, l'exécution de GC affecte les performances de l'application. Par exemple, pour les systèmes Web en temps réel tels que les jeux en ligne, les utilisateurs ne veulent pas que GC interrompt soudainement l'exécution des applications et effectue une collection de déprimations, nous devons alors ajuster les paramètres de GC afin que GC puisse libérer la mémoire de manière en douceur, comme la décomposition de la collection de déchets en une série de petites étapes à exécuter. Le hotspot JVM fourni par Sun prend en charge cette fonctionnalité.
Donne également un exemple typique de fuite de mémoire Java.
Vecteur v = nouveau vecteur (10); pour (int i = 1; i <100; i ++) {objet o = nouveau objet (); v.add (o); o = null; }Dans cet exemple, nous appliquons le cycle de l'objet objet et mettons l'objet appliqué dans un vecteur. Si nous libérons uniquement la référence elle-même, le vecteur fait toujours référence à l'objet, donc cet objet n'est pas recyclable pour GC. Par conséquent, si l'objet doit être supprimé du vecteur une fois qu'il est ajouté au vecteur, le moyen le plus simple est de définir l'objet vectoriel sur Null.
Fuite de mémoire dans Java détaillé
1. Mécanisme de recyclage de la mémoire Java
Quelle que soit la méthode d'allocation de mémoire de n'importe quelle langue, il est nécessaire de renvoyer l'adresse réelle de la mémoire allouée, c'est-à-dire renvoyer un pointeur à la première adresse du bloc de mémoire. Les objets en Java sont créés à l'aide de méthodes nouvelles ou de réflexion. La création de ces objets est allouée dans le tas. Tous les objets sont collectés par la machine virtuelle Java via un mécanisme de collecte des ordures. Afin de libérer correctement les objets, GC surveillera l'état de santé de chaque objet et surveillera leur application, leur citation, la citation, l'attribution, etc. Java utilisera des méthodes de graphe dirigées pour gérer la mémoire pour surveiller si l'objet peut être atteint en temps réel. S'il n'est pas atteint, il sera recyclé, ce qui peut également éliminer le problème des boucles de référence. Dans le langage Java, il existe deux types d'espace mémoire qui détermine si un espace mémoire répond aux critères de collecte des ordures: l'un doit attribuer une valeur vide à l'objet, qui n'a pas été appelé ci-dessous, et l'autre consiste à attribuer une nouvelle valeur à l'objet, réalisant ainsi l'espace mémoire.
2. Causes de fuite de mémoire Java
La fuite de mémoire fait référence à l'objet inutile continu (objet qui n'est plus utilisé) ou la mémoire d'objets inutiles ne peut pas être libérée dans le temps, ce qui entraîne un gaspillage d'espace mémoire, qui est appelé fuite de mémoire. Les fuites de mémoire ne sont parfois pas sérieuses et pas faciles à détecter, donc les développeurs ne savent pas qu'il y a une fuite de mémoire, mais parfois cela peut être très grave et vous incitera à sortir de la mémoire.
Quelle est la cause profonde de la fuite de mémoire Java? Si un objet de cycle à longue durée de vie détient une référence à un objet de cycle de court terme, il est probable qu'une fuite de mémoire se produira. Bien qu'un objet de cycle de courte durée ne soit plus nécessaire, il ne peut pas être recyclé car il détient sa référence pour un cycle de longue durée de vie. Il s'agit du scénario où les fuites de mémoire se produisent en Java. Il existe principalement les catégories suivantes:
1. La classe de collecte statique provoque une fuite de mémoire:
L'utilisation de hashmap, de vecteur, etc. est plus susceptible de se produire dans les fuites de mémoire. Le cycle de vie de ces variables statiques est cohérent avec celui de l'application. Tous les objets qu'ils font référence ne peuvent pas être libérés car ils seront également référencés par Vector, etc.
Par exemple
Vecteur statique v = nouveau vecteur (10); pour (int i = 1; i <100; i ++) {objet o = nouvel objet (); v.add (o); o = null;}Dans cet exemple, l'objet objet est une boucle appliquée et l'objet appliqué est placé dans un vecteur. Si la référence elle-même est uniquement libérée (o = null), le vecteur fait toujours référence à l'objet, donc cet objet n'est pas recyclable pour GC. Par conséquent, si l'objet doit être supprimé du vecteur une fois qu'il est ajouté au vecteur, le moyen le plus simple est de définir l'objet vectoriel sur Null.
2. Lorsque les propriétés d'objet de la collection sont modifiées, la méthode supprime () ne fonctionnera pas.
Par exemple:
public static void main (String [] args) {set <onom> set = new hashset <onom> (); personne p1 = new personne ("Tang Monk", "pwd1", 25); personne p2 = new personne ("Sun wukong", "pwd2", 26); personne p3 = nouvelle personne ("Zhu Bajie "," pwd3 ", 27); set.add (p1); set.add (p2); set.add (p3); system.out.println (" il y a un total de: "+ set.size () +" Elements! "); // Résultat: il y a un total de: 3 éléments! P3.Setage (2); // Modifiez l'âge de P3 et la valeur de code de hashCode correspondant aux changements de l'élément P3 à ce moment set.remove (p3); // Retirez-le à ce moment, provoquant des fuites de mémoire set.add (p3); // l'ajouter à nouveau et il a été ajouté avec succès System.out.println ("Il y a:" + set.size () + "Elements!"); // Résultat: il y a: 4 éléments au total! pour (personne personne: set) {System.out.println (personne);}}3. Écouteur
Dans la programmation Java, nous devons tous faire face aux auditeurs. Habituellement, de nombreux auditeurs sont utilisés dans une application. Nous appellerons une méthode de contrôle telle que addxxxListener () pour ajouter des auditeurs, mais souvent lorsque vous libérez l'objet, nous ne nous souvenons pas de supprimer ces auditeurs, augmentant ainsi les risques de fuites de mémoire.
4. Diverses connexions
Par exemple, la connexion de la base de données (DataSoue.getConnection ()), la connexion réseau (socket) et les connexions IO ne seront pas automatiquement recyclées par GC, sauf s'il appelle explicitement sa méthode close () pour fermer sa connexion. Les objets ResultSet et Instruction ne peuvent pas être explicitement recyclés, mais la connexion doit être explicitement recyclée car la connexion ne peut pas être automatiquement recyclée à tout moment. Une fois la connexion recyclée, les objets de résultat et d'instruction seront immédiatement nuls. Cependant, si vous utilisez un pool de connexions, la situation est différente. En plus de fermer explicitement la connexion, vous devez également fermer explicitement l'objet d'instruction ResultSet (en fermant l'un d'eux, l'autre sera également fermé), sinon un grand nombre d'objets d'instruction ne seront pas libérés, provoquant des fuites de mémoire. Dans ce cas, la connexion sera généralement publiée dans l'essai et enfin.
5. Références aux classes internes et aux modules externes
Les références aux classes internes sont relativement faciles à oublier, et une fois qu'elles ne sont pas publiées, une série d'objets de classe successeur peut ne pas être publié. De plus, les programmeurs doivent également faire attention aux références inadvertes aux modules externes. Par exemple, le programmeur A est responsable du module A et appelle une méthode du module B tel que:
public void registermsg (objet b);
Ce type d'appel nécessite un grand soin. Lorsqu'un objet est passé, il est très probable que le module B conserve une référence à l'objet. Pour le moment, vous devez faire attention à savoir si le module B fournit des opérations correspondantes pour supprimer les références.
6. Mode singleton
Une utilisation incorrecte du motif de singleton est un problème courant qui provoque des fuites de mémoire. Les objets singleton existeront tout au long du cycle de vie du JVM après initialisation (sous la forme de variables statiques). Si l'objet Singleton contient des références externes, cet objet ne sera pas recyclé normalement par le JVM, entraînant des fuites de mémoire. Considérez l'exemple suivant:
classe A {public a () {b.getInstance (). seta (this);} ....} // classe B utilise le mode singleton classe b {private a a; private static b instance b = new b (); public b () {} public static b getInstance () {return instance;} public Void seta (a a) {this.a = a;} / getter ...De toute évidence, B adopte le modèle singleton, qui contient une référence à un objet A, et l'objet de cette classe A ne sera pas recyclé. Imaginez ce qui se passerait si A était un objet ou un type de collection plus complexe
Résumé des fuites de mémoire communes dans Android
Fuite de classe de collection
Si la classe de collecte n'a qu'une méthode pour ajouter des éléments et n'a pas de mécanisme de suppression correspondant, la mémoire sera occupée. Si cette classe de collecte est une variable globale (telle que les propriétés statiques dans la classe, la carte globale, etc., c'est-à-dire qu'il y a une référence statique ou un pointage final vers lui tout le temps), il n'y a pas de mécanisme de suppression correspondant, ce qui peut entraîner une diminution et non de ne pas diminuer. Par exemple, l'exemple typique ci-dessus est l'une de ces situations. Bien sûr, nous n'écrirons certainement pas un tel code 2B dans le projet, mais il est toujours facile de se produire si nous ne faisons pas attention. Par exemple, nous aimons tous faire des caches via Hashmap, nous devons donc être plus prudents dans cette situation.
Fuite de mémoire causée par des singletons
Parce que la nature statique d'un singleton fait son cycle de vie tant que le cycle de vie de l'application, s'il est utilisé de manière inappropriée, il est facile de provoquer une fuite de mémoire. Par exemple, l'exemple typique suivant,
classe publique AppManager {instance private static AppManager; Contexte privé; Context Private AppManager (Context Context) {this.context = context;} public static AppManager GetInstance (context Context) {if (instance == null) {instance = new AppManager (context);} return instance;}}Il s'agit d'un schéma singleton normal. Lors de la création de ce singleton, car un contexte doit être transmis, la durée du cycle de vie de ce contexte est cruciale:
1. Si le contexte de l'application est passé pour le moment, car le cycle de vie de l'application est le cycle de vie de l'ensemble de l'application, il n'y aura aucun problème.
2. Si le contexte d'activité est passé à l'heure actuelle, lorsque l'activité correspondant à ce contexte sort, car la référence au contexte est maintenue par un objet Singleton, son cycle de vie est égal à l'ensemble du cycle de vie de l'application, donc lorsque l'activité sort, sa mémoire ne sera pas recyclée, ce qui provoque une fuite.
La bonne façon doit être changée par ce qui suit:
classe publique AppManager {instance private static AppManager; contexte de contexte privé; application privée (contexte de contexte) {this.context = context.getApplicationContext (); // contexte utilisant l'application} public static appmanager getInstance (contexte context) {if (instance == null) {instance = new appManager (contexte);} retour instance;}}}Ou écrivez de cette façon, et vous n'avez même pas besoin de passer le contexte:
Ajoutez une méthode statique à votre application, getContext () renvoie le contexte de l'application.
...
context = getApplicationContext (); ... / *** Get Global Context * @return return Global Context Object * / public static context getContex {instance = new AppManager ();} Retour instance;}}Classes intérieures anonymes / classes intérieures non statiques et fils asynchrones
Fuite de mémoire causée par la création d'instances statiques dans les classes internes non statiques
Parfois, nous pouvons commencer fréquemment des activités. Afin d'éviter de créer à plusieurs reprises les mêmes ressources de données, cette façon d'écrire peut se produire:
classe publique MainActivity étend AppCompatActivity {private static testResource mresource = null; @OverRideProtected void onCreate (bundle SavedInstanceState) {super.Oncreate (SavedInstancestate); setContentView (R.Layout); TestResource ();} // ...} classe TestResource {// ...}}Cela crée un singleton d'une classe intérieure non statique à l'intérieur de l'activité, et les données du singleton sont utilisées chaque fois que l'activité est démarrée. Bien que la création répétée de ressources soit évitée, cette écriture provoquera des fuites de mémoire, car la classe interne non statique conservera des références aux classes externes par défaut, et la classe interne non statique créera une instance statique, et le cycle de vie de l'instance est aussi longue que l'application, ce qui ne peut pas être recyclé statique pour toujours. La bonne façon de le faire est:
Définissez la classe intérieure en tant que classe intérieure statique ou extrayez la classe intérieure et encapsulez-la en singleton. Si vous devez utiliser le contexte, veuillez suivre le contexte recommandé ci-dessus pour utiliser l'application. Bien sûr, le contexte de l'application n'est pas omnipotent, il ne peut donc pas être utilisé au hasard. Dans certains endroits, vous devez utiliser le contexte de l'activité. Les scénarios d'application du contexte de l'application, du service et de l'activité sont les suivants:
Où: No1 signifie que l'application et le service peuvent démarrer une activité, mais une nouvelle file d'attente de tâches doit être créée. Pour la boîte de dialogue, il ne peut être créé qu'en activité
Classe interne anonyme
Le développement Android hérite souvent de la mise en œuvre de l'activité / fragment / vue. Pour le moment, si vous utilisez des classes anonymes et que vous êtes tenu par des fils asynchrones, soyez prudent. S'il n'y a pas de mesure, cela entraînera certainement des fuites.
La classe publique MainActivity étend l'activité {... Runnable Ref1 = new MyRunable (); Runnable Ref2 = new Runnable () {@OverRidePublic void run () {}}; ...}La différence entre Ref1 et Ref2 est que Ref2 utilise des classes intérieures anonymes. Jetons un coup d'œil à la mémoire référencée au moment de l'exécution:
Comme vous pouvez le voir, Ref1 n'a rien de spécial.
Mais il y a une référence supplémentaire dans l'objet d'implémentation de la classe anonyme Ref2:
Cette référence de 0 $ pointe vers la mainactivité. Si cette référence est transmise dans un fil asynchrone, et ce fil et ce cycle de vie de l'activité sont incohérents, la fuite d'activité sera causée.
Fuite de mémoire causée par le gestionnaire
Le problème de fuite de mémoire causé par l'utilisation du gestionnaire doit être considéré comme le plus courant. Afin d'éviter l'ANR, nous ne effectuons pas de chronologies sur le thread principal et utilisons le gestionnaire pour gérer les tâches réseau ou encapsuler certains rappels de demande et autres API. Cependant, le gestionnaire n'est pas omnipotent. Si le code du gestionnaire est écrit de manière standardisée, il peut entraîner des fuites de mémoire. De plus, nous savons que le gestionnaire, le message et le messageQueue sont tous liés les uns aux autres. Dans le cas où le message envoyé par le gestionnaire n'a pas encore été traité, le message et l'objet de gestionnaire qui l'ont envoyé seront détenus par le thread MessageQueue.
Étant donné que le gestionnaire appartient aux variables TLS (stockage de stockage local), le cycle de vie et l'activité sont incohérents. Par conséquent, cette méthode de mise en œuvre est généralement difficile pour s'assurer qu'elle est cohérente avec le cycle de vie de la vue ou de l'activité, il est donc facile de provoquer la bonne version.
Par exemple:
classe publique SampleActivity étend l'activité {le gestionnaire final privé MleakyHandler = new Handler () {@OverRiDePublic void HandleMessage (Message Msg) {// ...}} @ OverRideprotected void onCreate (bundle SaupedInstanCestate) {Super.oncreate (SavEdInstanchestate); // post a message et retrache pour 10 Minthture.MleakyHandler.PostDelayed (new Runnable () {@OverRidePublic void run () {/ * ... * /}}, 1000 * 60 * 10); // retourner à l'activité précédente.Finish ();}}Une exécution retardée de message de 10 minutes est déclarée dans la SampleActivité, et MleakyHandler le pousse dans le message de file d'attente de message. Lorsque l'activité est abandonnée par Finish (), le message qui retarde l'exécution de la tâche continuera d'exister dans le thread principal, qui maintient la référence du gestionnaire de l'activité, de sorte que l'activité est tombée par finition () ne sera pas recyclée, provoquant des fuites de mémoire (car le gestionnaire est une classe intérieure non statique, il tiendra des références à la classe externe, qui se réfère à SAMPLACTIVITÉ) ici).
Fix: Évitez d'utiliser des classes internes non statiques dans l'activité. Par exemple, si nous déclarons le gestionnaire comme statique ci-dessus, sa période de survie n'a rien à voir avec le cycle de vie de l'activité. Dans le même temps, l'activité est introduite par des références faibles pour éviter de passer directement l'activité comme contexte. Voir le code suivant:
classe publique Sampleactivité étend l'activité {/ *** Instances de classes internes statiques ne tiennent pas une référence implicite * à leur classe extérieure. * / classe statique privée MyHandler étend le gestionnaire {une activité de sampleactivité finale) {MActivity = New Wewleference <SampleActivité> (activité);}} HandleMessage (message msg) {sampleActivity Activity = mActivity.get (); if (Activity! = null) {// ...}}} private final myHandler mhandler = new MyHandler (this); / *** instances of static " {@OverRidePublic void run () {/ * ... * /}}; @ OverRideProtected void onCreate (bundle SavedInstancEstate) {super.oncreate (SavedInstanceState); // publie un message et retarde son exécution pendant 10 minutes. Activité.finish ();}}Présentation, il est recommandé d'utiliser la classe intérieure statique + la référence faible. Faites attention à être vide avant chaque utilisation.
La référence faible a été mentionnée plus tôt, alors je parlerai brièvement de plusieurs types de référence d'objets Java.
Java a quatre catégories de références: une forte référence, une référence, une référence faible et une phatomréférence.
Dans le développement d'applications Android, afin d'éviter le débordement de la mémoire, lorsqu'il s'agit de certains objets qui occupent une grande mémoire et ont un long cycle de déclaration, des technologies de référence douce et de référence faible peuvent être utilisées autant que possible.
Des références douces / faibles peuvent être utilisées conjointement avec une file d'attente de référence (ReferenceQueue). Si l'objet référencé par la référence douce est recyclé par le collecteur de déchets, la machine virtuelle Java ajoutera la référence douce à la file d'attente de référence associée. Cette file d'attente vous permet de connaître la liste recyclée des références douces / faibles, éliminant ainsi le tampon qui a échoué des références douces / faibles.
Supposons que notre application utilise un grand nombre d'images par défaut, telles que l'avatar par défaut, l'icône de jeu par défaut, etc., qui sera utilisée à de nombreux endroits. Si vous lisez l'image à chaque fois, il sera plus lent car la lecture du fichier nécessite un fonctionnement matériel, ce qui entraînera des performances plus faibles. Nous considérons donc le cache de l'image et le lisons directement à partir de la mémoire en cas de besoin. Cependant, comme les images occupent beaucoup d'espace mémoire et se cachent, de nombreuses images nécessitent beaucoup de mémoire, les exceptions d'overfMemory peuvent être plus susceptibles de se produire. Pour le moment, nous pouvons envisager d'utiliser des techniques de référence douces / faibles pour éviter ce problème. Ce qui suit est le prototype du cache:
Définissez d'abord un hashmap et enregistrez l'objet de référence doux.
map privé <chaîne, softreference <bitmap>> ImageCache = new Hashmap <String, softreference <bitmap>> ();
Définissons une méthode pour enregistrer la référence douce du bitmap à Hashmap.
Après avoir utilisé des références souples, avant que l'exception d'OutofMemory ne se produise, l'espace mémoire de ces ressources d'image en cache peut être libéré, empêchant ainsi la mémoire d'atteindre la limite supérieure et d'éviter le crash.
Si vous souhaitez simplement éviter la survenue d'une exception en outre, vous pouvez utiliser des références douces. Si vous vous souciez davantage des performances de votre application et que vous souhaitez recycler certains objets qui occupent plus de mémoire dès que possible, vous pouvez utiliser des références faibles.
De plus, vous pouvez déterminer si l'objet est fréquemment utilisé pour déterminer s'il est sélectionné pour une référence douce ou une référence faible. Si l'objet peut être utilisé fréquemment, essayez d'utiliser des références douces. Si l'objet n'est pas plus probable, il peut être utilisé avec des références faibles.
Ok, continuez à revenir au sujet. Comme mentionné précédemment, créez une classe intérieure de gestionnaire statique et utilisez des références faibles aux objets détenus par le gestionnaire, afin que les objets maintenus par le gestionnaire puissent également être recyclés lors du recyclage. Cependant, bien que cela évite les fuites d'activité, il peut toujours y avoir des messages en attente dans la file d'attente de messages du thread Looper, nous devons donc supprimer les messages dans la file d'attente de messages MessageQueue pendant la destruction ou l'arrêt de l'activité.
Les méthodes suivantes peuvent supprimer le message:
RemoveCallbacks du vide final public (Runnable R); Public Final Void RemoveCallbacks (Runnable R, OBJET TOKEN); Public final vide RemoveCallbacksandMessages (Token d'objet); Removemessages du vide final public (Int What); RemoveMessages de vide final public (int, quoi, objet objet);
Essayez d'éviter d'utiliser des variables de membres statiques
Si une variable de membre est déclarée statique, nous savons tous que son cycle de vie sera le même que l'ensemble du cycle de vie du processus d'application.
Cela entraînera une série de problèmes. Si le processus de votre application est conçu pour être résident à la mémoire, même si l'application passe à l'arrière-plan, cette partie de la mémoire ne sera pas publiée. Selon le mécanisme actuel de gestion de la mémoire des applications mobiles, les processus de fond qui tiennent compte d'une grande quantité de mémoire seront recyclés en premier. Si cette application a effectué une protection mutuelle des processus, elle entraînera le redémarrage fréquemment de l'application en arrière-plan. Lorsque le téléphone installe l'application que vous avez participé au développement, le téléphone consomme de l'alimentation et du trafic pendant la nuit, et votre application doit être désinstallée ou silencieuse par l'utilisateur.
Le correctif ici est:
N'initialisez pas les membres statiques au début de la classe. L'initialisation paresseuse peut être prise en compte.
Dans la conception architecturale, nous devons nous demander s'il est vraiment nécessaire de le faire et d'essayer de l'éviter. Si l'architecture doit être conçue comme ceci, vous avez la responsabilité de gérer le cycle de vie de cet objet.
Évitez la remplacement de finalisation ()
1. La méthode finalisée est exécutée à un moment incertain et ne peut pas être invoqué pour libérer des ressources rares. Les raisons du temps incertain sont:
Le moment où la machine virtuelle appelle GC est incertaine
Le moment où le fil de démon finaliser est prévu est incertain
2. La méthode Finalise ne sera exécutée qu'une seule fois. Même si l'objet est ressuscité, si la méthode Finalise a été exécutée, elle ne sera plus exécutée lors de son GC. La raison en est:
L'objet contenant la méthode Finalise génère une référence finalisée par la machine virtuelle lorsqu'elle est nouvelle, et fait référence à l'objet. Lorsque la méthode finalisée est exécutée, la référence finalisée correspondant à l'objet sera libérée. Même si l'objet est ressuscité pour le moment (c'est-à-dire en faisant référence à l'objet avec une référence forte), et la deuxième fois qu'il est GC, car la référence finalisée ne correspond plus à lui, la méthode finalisée ne sera pas exécutée.
3. Un objet contenant la méthode Finalise doit passer par au moins deux tours de GC avant de pouvoir être libéré.
Fuite de mémoire causée par des ressources non clôturées
对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
Par exemple:
Bitmap 没调用recycle()方法,对于Bitmap 对象在不使用时,我们应该先调用recycle() 释放内存,然后才它设置为null. 因为加载Bitmap 对象的内存空间,一部分是java 的,一部分C 的(因为Bitmap 分配的底层是通过JNI 调用的)。 而这个recyle() 就是针对C 部分的内存释放。
构造Adapter 时,没有使用缓存的convertView ,每次都在创建新的converView。这里推荐使用ViewHolder。
Résumer
对Activity 等组件的引用应该控制在Activity 的生命周期之内; 如果不能就考虑使用getApplicationContext 或者getApplication,以避免Activity 被外部长生命周期的对象引用而泄露。
尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量
Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空Handler 里面的消息。比如在Activity onStop 或者onDestroy 的时候,取消掉该Handler 对象的Message和Runnable.
在Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为null,比如使用完Bitmap 后先调用recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
The above is a summary of the causes of memory leaks in Java introduced to you by the editor and how to avoid memory leaks (super detailed version). J'espère que ce sera utile à tout le monde. Si vous avez des questions, veuillez me laisser un message et l'éditeur vous répondra à temps. Merci beaucoup pour votre soutien au site Web Wulin.com!