Cet article présente les principes de la gestion de la mémoire Java et les causes des fuites de mémoire en détail.
Mécanisme de gestion de la mémoire Java
En C ++, si un morceau de mémoire est nécessaire pour allouer dynamiquement un morceau de mémoire, le programmeur doit être responsable de tout le cycle de vie de ce morceau de mémoire. De la demande d'allocation, d'utilisation, de libération finale. Ce processus est très flexible, mais très lourd. La langue Java a fait sa propre optimisation pour la gestion de la mémoire, qui est le mécanisme de collecte des ordures. Presque tous les objets de mémoire de Java sont alloués sur la mémoire du tas (sauf pour les types de données de base), et GC (collection Gage) est alors responsable du recyclage automatique de la mémoire qui n'est plus utilisée.
Ce qui précède est la situation de base du mécanisme de gestion de la mémoire Java. Mais si nous ne comprenons que cela, nous rencontrerons toujours des fuites de mémoire dans le développement réel du projet. Certaines personnes peuvent douter que, puisque le mécanisme de collecte des ordures de Java peut recycler automatiquement la mémoire, pourquoi y aura-t-il encore des fuites de mémoire? Dans cette question, nous devons savoir quand GC recycle les objets de mémoire et quel type d'objets de mémoire sera considéré comme "non utilisé" par GC.
L'accès aux objets de mémoire dans Java utilise des méthodes de référence. Dans Java Code, nous maintenons une variable de référence d'un objet de mémoire. Dans les programmes Java, cette variable de référence elle-même peut être stockée dans la mémoire du tas et dans la mémoire de la pile de code (comme le type de données de base). Les threads GC commencent à suivre à partir des variables de référence dans la pile de code pour déterminer quelle mémoire est utilisée. Si le thread GC ne peut pas suivre un morceau de mémoire de tas de cette manière, le GC estime que ce morceau de mémoire ne sera plus utilisé (car le code ne peut plus accéder à ce morceau de mémoire).
Grâce à cette méthode de gestion de la mémoire graphique dirigée, lorsqu'un objet de mémoire perd toutes les références, GC peut le recycler. Inversement, si l'objet a toujours une référence, il ne sera pas recyclé par GC, même si la machine virtuelle Java lance OutOfMemoryError.
Fuite de mémoire Java
D'une manière générale, il existe deux situations pour les fuites de mémoire. Dans un cas, dans le langage C / C ++, toute la mémoire allouée dans le tas sera supprimée (comme la réaffectation du pointeur) lorsqu'il n'est pas libéré; mémoire et sa méthode d'accès (référence). Le premier cas est qu'il a été bien résolu à Java en raison de l'introduction du mécanisme de collecte des ordures. Par conséquent, les fuites de mémoire en Java se réfèrent principalement au deuxième cas.
Peut-être que parler du concept est trop abstrait, vous pouvez jeter un œil à un tel exemple:
La copie de code est la suivante:
Vecteur v = nouveau vecteur (10);
pour (int i = 1; i <100; i ++) {
Objet o = nouvel objet ();
v.add (o);
o = null;
}
Dans cet exemple, il existe des références à Vector Object V et des références à l'objet objet O dans la pile de code. Dans la boucle FOR, nous générons en continu de nouveaux objets, puis les ajoutons à l'objet vectoriel, puis vidons la référence O. La question est que si GC se produit une fois la référence O vide, l'objet objet que nous créons sera recyclé par GC? La réponse est non. Étant donné que lorsque GC suit les références dans la pile de code, il trouvera une référence V et continue de suivre, il constatera qu'il y a une référence à l'objet objet dans l'espace mémoire pointé par la référence V. C'est-à-dire que, bien que la référence O ait été vide, il existe encore d'autres références dans l'objet objet et accessibles, de sorte que le GC ne peut pas le libérer. Si l'objet objet n'a aucun effet sur le programme après cette boucle, nous pensons qu'une fuite de mémoire s'est produite dans ce programme Java.
Bien que les fuites de mémoire Java soient moins destructrices pour les fuites de mémoire en C / C ++, le programme peut toujours s'exécuter normalement dans la plupart des cas, à l'exception de quelques cas où le programme se bloque. Cependant, lorsque les appareils mobiles ont des restrictions strictes sur la mémoire et le CPU, le débordement de la mémoire Java entraînera l'inefficacité des programmes et l'occupation de grandes quantités de mémoire indésirable. Cela entraînera la détérioration des performances de l'ensemble de la machine et, dans les cas graves, cela entraînera également le lancement de la perpétuité de l'OutofMemoryerror, ce qui entraînera un acte de s'écraser le programme.
Évitez les fuites de mémoire en général
En général, sans impliquer des structures de données complexes, les fuites de mémoire Java se manifestent car le cycle de vie d'un objet de mémoire dépasse la durée dont le programme en a besoin. Nous l'appelons parfois "sans objet".
Par exemple:
La copie de code est la suivante:
classe publique Filesearch {
contenu octet privé [];
fichier privé mFile;
Public Filesearch (fichier de fichier) {
mFile = fichier;
}
Hassstring booléen public (String Str) {
int size = getFileSize (mFile);
contenu = nouveau octet [taille];
LoadFile (Mfile, contenu);
String s = new String (contenu);
Retour S.Contains (STR);
}
}
Dans ce code, il y a une fonction hasstring dans la classe Filesearch pour déterminer si le document contient la chaîne spécifiée. Le processus consiste à charger MFILE en mémoire d'abord, puis à faire des jugements. Cependant, le problème ici est que le contenu est déclaré comme variable d'instance, pas comme une variable locale. Ainsi, après le retour de cette fonction, les données de l'ensemble du fichier existent toujours en mémoire. Il est évident que nous n'avons plus besoin de ces données à l'avenir, ce qui conduit à un gaspillage de mémoire déraisonnable.
Pour éviter les fuites de mémoire dans ce cas, nous devons gérer notre mémoire allouée avec la pensée de gestion de la mémoire C / C ++. Tout d'abord, il s'agit de clarifier la portée effective de l'objet mémoire avant de déclarer la référence de l'objet. Les objets de mémoire valides dans une fonction doivent être déclarés comme variables locales, et ceux qui ont le même cycle de vie que l'instance de classe doivent être déclarés sous forme de variables d'instance ... et ainsi de suite. Deuxièmement, n'oubliez pas de vider manuellement l'objet mémoire lorsqu'il n'est plus nécessaire.
Problème de fuite de mémoire dans des structures de données complexes
Dans les projets réels, nous utilisons souvent des structures de données plus complexes pour mettre en cache les informations de données nécessaires pendant le fonctionnement du programme. Parfois, en raison de la complexité de la structure des données, ou nous avons des besoins spéciaux (par exemple, autant d'informations sur le cache que possible pour améliorer la vitesse d'exécution du programme, etc.), il est difficile pour nous de traiter les données dans la structure des données. Pour le moment, nous pouvons utiliser un mécanisme spécial en Java pour éviter les fuites de mémoire.
Nous avons déjà introduit que le mécanisme GC de Java est basé sur le mécanisme de référence qui suit la mémoire. Avant cela, les références que nous avons utilisées ne définissaient qu'une forme de "objet o;". En fait, il s'agit d'une situation par défaut dans le mécanisme de référence Java, et il existe d'autres méthodes de référence. En utilisant ces mécanismes de référence spéciaux et en combinant avec le mécanisme GC, nous pouvons réaliser certains des effets dont nous avons besoin.
Plusieurs méthodes de référence en Java
Il existe plusieurs façons de citer dans Java, à savoir: citation forte, citation douce, citation faible et citation virtuelle. Ensuite, nous comprenons d'abord la signification de ces méthodes de citation en détail.
Citation forte
Les citations utilisées dans le contenu que nous avons introduit auparavant étaient toutes des citations fortes, qui sont les citations les plus courantes utilisées. Si un objet a une forte référence, il est similaire à une nécessité quotidienne essentielle, et le collecteur des ordures ne le recyclera jamais. Lorsque l'espace mémoire est insuffisant, la machine virtuelle Java préfère lancer une erreur d'OutofMemoryError pour que le programme se termine anormalement que de recycler des objets avec de fortes références pour résoudre le problème de la mémoire.
Perfectionnement
Une utilisation typique de la classe Softreference est pour les caches sensibles à la mémoire. Le principe de Softreference est de s'assurer que toutes les références douces sont effacées avant que le JVM ne signale une mémoire insuffisante lors de la référence à un objet. Le point clé est que le collecteur des ordures peut (ou peut non) libérer des objets accessibles à doux au moment de l'exécution. Le fait qu'un objet soit libéré dépend de l'algorithme du collecteur des ordures et de la quantité de mémoire disponible lorsque le collecteur des ordures est en cours d'exécution.
Référence faible
Une utilisation typique de la classe de référence faible consiste à normaliser la cartographie (cartographie canonicalisée). De plus, les références faibles sont également utiles pour les objets avec des durées de vie relativement longues et des frais généraux de loisirs. Le point clé est que si un objet faiblement accessible est rencontré lors de l'exécution du collecteur de déchets, l'objet référencé par faible référence sera libéré. Cependant, notez que le collecteur des ordures peut devoir s'exécuter plusieurs fois avant de pouvoir trouver et libérer des objets faiblement accessibles.
Fantôme
La classe Phantomreference ne peut être utilisée que pour suivre les collections à venir d'objets référencés. De même, il peut également être utilisé pour effectuer des opérations de compensation pré-mortem. Phantomreference doit être utilisé avec la classe ReferenceQueue. ReferenceQueue est requis car il peut agir comme un mécanisme de notification. Lorsque le collecteur de déchets détermine qu'un objet est un objet d'accès virtuel, l'objet PhantomReference est placé sur sa référence. La mise en place de l'objet Phantomreference sur la référence est une notification indiquant que l'objet référencé par l'objet Phantomreference est terminé et est disponible pour la collecte. Cela vous permet d'agir juste avant que la mémoire occupée par l'objet ne soit recyclé. La référence et la référence sont utilisées en conjonction avec ReferenceQueue.
GC, référence et interaction de référence
A. GC ne peut pas supprimer la mémoire des objets avec de fortes références.
B. GC a trouvé une mémoire d'objet avec seulement des références douces, puis:
① Le champ de référence de l'objet Softreference est défini sur NULL, de sorte que l'objet ne fait plus référence à l'objet tas.
② L'objet tas référencé par Softreference est déclaré finalisable.
③ Lorsque la méthode finaliser () de l'objet HEAP est exécutée et que la mémoire occupée par l'objet est libérée, l'objet Softreference est ajouté à sa référence (si ce dernier existe).
C. GC découvre une mémoire d'objet avec seulement des références faibles, alors:
① Le champ de référence de l'objet FaibleRreference est défini sur NULL, de sorte que l'objet ne fait plus référence à l'objet tas.
② L'objet de tas référencé par faible référence est déclaré finalisable.
③ Lorsque la méthode finalisée () de l'objet HEAP est exécutée et que la mémoire occupée par l'objet est libérée, l'objet FaibleReference est ajouté à sa référence (si ce dernier existe).
D. GC découvre une mémoire d'objet qui n'a que des références virtuelles, alors:
① L'objet de tas référencé par Phantomreference est déclaré finalisable.
② Phantomreference est ajouté à sa référence de référence avant la libération de l'objet tas.
Les points suivants méritent d'être notés:
1. GC ne trouvera pas les objets de mémoire référencés en douceur en général.
2. La découverte et la libération de GC de références faibles ne sont pas immédiatement.
3. Lorsque des références souples et des références faibles sont ajoutées à ReferenceQueue, les références à la mémoire réelle ont été définies pour vider et la mémoire pertinente a été publiée. Lors de l'ajout de référence virtuelle à ReferenceQueue, la mémoire n'a pas encore été publiée et peut encore être accessible.
Grâce à l'introduction ci-dessus, je crois que vous avez une certaine compréhension du mécanisme de citation Java et des similitudes et des différences de plusieurs méthodes de citation. Un concept peut être trop abstrait.
La copie de code est la suivante:
String str = new String ("Hello");
ReferenceQueue <string> rq = new Referenceeue <string> ();
FaibleReference <string> wf = new FaibleReference <string> (str, rq);
str = null;
String str1 = wf.get ();
// Si l'objet "Hello" n'est pas recyclé, rq.poll () renvoie nul
Référence <?
Dans le code ci-dessus, faites attention aux deux endroits ⑤⑥. Si l'objet "Hello" n'est pas recyclé wf.get () renvoie l'objet "Hello", rq.poll () renvoie null; Null, rq.poll () renvoie un objet de référence, mais il n'y a pas de référence à l'objet STR dans cet objet de référence (la phantomréférence est différente de la référence faible et de la softreference).
Application conjointe du mécanisme de citation et des structures de données complexes
En comprenant le mécanisme GC, le mécanisme de référence et la combinaison avec ReferenceQueue, nous pouvons implémenter certains types de données complexes qui empêchent le débordement de la mémoire.
Par exemple, Softreference a les caractéristiques de la construction d'un système de cache, nous pouvons donc implémenter un système de cache simple en combinaison avec des tables de hachage. Cela garantit non seulement que autant d'informations peuvent être mises en cache, mais garantissent également que la machine virtuelle Java ne lancera pas la perpétuité en raison de la fuite de la mémoire. Ce mécanisme de mise en cache est particulièrement adapté aux situations où les objets de mémoire ont un long cycle de vie et le temps de générer des objets de mémoire est relativement long, tel que les images de couverture de la liste de cache, etc. Pour certains cas où le cycle de vie est long mais les frais généraux de génération d'objets de mémoire n'est pas important, l'utilisation de la référence faible peut atteindre une meilleure gestion de la mémoire.
Une copie du code source de SofthashMap est jointe.
La copie de code est la suivante:
package com. *** .widget;
//: softhashmap.java
importer java.util.
import java.lang.ref.
import android.util.log;
classe publique SofthashMap étend AbstractMap {
/ ** Le hashmap interne qui tiendra la Softreference.
Private Final Map Hash = new HashMap ();
/ ** Le nombre de références "dures" pour tenir en interne.
private final int hard_size;
/ ** La liste FIFO des références dures, ordre du dernier accès.
Private Final LinkedList hardcache = new LinkedList ();
/ ** FIDE DE RÉFÉRENCE POUR LES OBJETS DE SOFTREFENCE ENCORET.
Private ReferenceQueue Queue = new ReferenceQueue ();
// Numéro de référence fort
public softhashmap () {this (100);};
public softhashmap (int hardsize) {hard_size = Hardsize;
objet public get (clé d'objet) {
Résultat de l'objet = null;
// Nous obtenons la Softreference représentée par cette clé
SofTreference soft_ref = (softreference) hash.get (key);
if (soft_ref! = null) {
// D'après la Softreference, nous obtenons la valeur, qui peut être
// null s'il n'était pas sur la carte, ou il a été supprimé dans
// la méthode processQueue () définie ci-dessous
result = soft_ref.get ();
if (result == null) {
// Si la valeur a été collectée aux ordures, supprimez le
// Entrée du hashmap.
Hash.Remove (clé);
} autre {
// nous ajoutons maintenant cet objet au début du dur
// la file d'attente de référence.
// Une fois, car les recherches de la file d'attente FIFO sont lentes, donc
// Nous ne voulons pas le rechercher à chaque fois pour supprimer
// doublons.
// Gardez l'objet usage récent en mémoire
hardcache.addFirst (résultat);
if (hardcache.size ()> hard_size) {
// supprime la dernière entrée si la liste plus longue que Hard_Size
hardcache.removelast ();
}
}
}
Résultat de retour;
}
/ ** Nous définissons notre propre sous-classe de Softreference qui contient
Non seulement la valeur mais aussi la clé pour le rendre plus facile à trouver
L'entrée dans le hashmap après la collecte des ordures.
classe statique privée softvalue étend la malice {
Clé d'objet final privé;
/ ** Saviez-vous qu'une classe extérieure peut accéder aux données privées
Membres et méthodes d'une classe intérieure?
Je pensais que ce n'était que la classe intérieure qui pouvait accéder au
les informations privées de la classe extérieure.
accéder aux membres privés d'une classe intérieure à l'intérieur de son intérieur
classe. */
SoftValue privé (objet K, clé d'objet, ReferenceQueue Q) {
super (k, q);
ce .key = key;
}
}
/ ** Ici, nous passons par la référence et supprimez les ordures
collecté des objets SoftValue sur le hashmap en les regardant
Utilisation du membre de données SoftValue.Key.
public void processQueue () {
SoftValue SV;
while ((sv = (softvalue) file d'attente.poll ())! = null) {
if (sv.get () == null) {
Log.e ("processQueue", "null");
} autre {
Log.e ("processQueue", "pas null");
}
Hash.Remove (sv.Key);
Log.e ("softhashmap", "release" + sv.key);
}
}
/ ** Ici, nous mettons la paire de valeurs de clé dans le hashmap en utilisant
un objet SoftValue.
Public objet put (clé d'objet, valeur objet) {
processQueue ();
Log.e ("softhashmap", "mis dans" + key);
return hash.put (clé, new softValue (valeur, clé, file d'attente));
}
Objet public Supprimer (clé d'objet) {
processQueue ();
return hash.remove (key);
}
public void clear () {
hardcache.clear ();
processQueue ();
hash.clear ();
}
public int size () {
processQueue ();
return hash.size ();
}
Public set entryset () {
// Non, non, vous ne pouvez pas faire ça !!!
Jetez un nouveau non soutenue par rapport à ();
}
}