Une caractéristique importante de Java consiste à gérer automatiquement le recyclage de la mémoire via un collecteur de déchets (GC), sans obliger les programmeurs à libérer de la mémoire par eux-mêmes. Théoriquement, toute la mémoire occupée par des objets qui ne seront plus utilisés en Java peuvent être recyclés par GC, mais Java a également des fuites de mémoire, mais ses performances sont différentes de C ++.
Gestion de la mémoire en Java
Pour comprendre les fuites de mémoire en Java, vous devez d'abord savoir comment la mémoire de Java est gérée.
Dans les programmes Java, nous utilisons généralement des nouveaux pour allouer de la mémoire aux objets, et ces espaces de mémoire sont sur le tas (tas).
Voici un exemple:
classe publique Simple {public static void main (String args []) {objet objet1 = nouveau objet (); // obj1 objet objet2 = new object (); // obj2 objet2 = objet1; //...At cette fois, l'OBJ2 peut être nettoyé}}Java utilise des graphiques dirigés pour la gestion de la mémoire:
Dans le graphique dirigé, nous appelons Obj1 pour être accessible, OBJ2 pour être inaccessible, et évidemment inaccessible peut être nettoyé.
La libération de la mémoire, c'est-à-dire le nettoyage d'objets inaccessibles, est déterminé et exécuté par GC, donc GC surveillera l'état de chaque objet, y compris l'application, la citation, la citation et l'attribution. Le principe fondamental de la libération d'un objet est que l'objet ne sera plus utilisé:
L'objet reçoit une valeur nulle, et il n'a plus jamais été appelé.
L'autre consiste à attribuer une nouvelle valeur à l'objet, qui réallume l'espace mémoire.
Habituellement, on pense que le coût de l'allocation d'objets sur le tas est relativement élevé, mais GC optimise cette opération: en C ++, allouant un morceau de mémoire sur le tas recherchera un morceau de mémoire approprié à allouer. Si l'objet est détruit, ce morceau de mémoire peut être réutilisé; En Java, vous voulez une longue bande. Chaque fois qu'un nouvel objet est alloué, le "pointeur de tas" de Java recule vers une zone qui n'a pas été allouée. Par conséquent, l'efficacité de la mémoire d'allocation de Java est comparable à celle de C ++.
Mais il y a un problème avec cette façon de travailler: si la mémoire est fréquemment appliquée, les ressources seront épuisées. À l'heure actuelle, GC intervient, il récupére l'espace et rend les objets dans le tas plus compacts. De cette façon, il y aura toujours suffisamment d'espace mémoire à allouer.
Méthode de comptage de référence Lorsque GC nettoie: Lorsqu'une référence est connectée à un nouvel objet, le nombre de références est +1; Lorsqu'une référence quitte la portée ou est définie sur NULL, le nombre de références est -1. Lorsque GC constate que ce nombre est de 0, il recycle la mémoire qu'il consomme. Ce frais généraux se produit tout au long de la vie du programme de référence et ne peut pas gérer les références circulaires. Cette méthode est donc juste utilisée pour illustrer le fonctionnement de GC et ne sera appliqué par aucune machine virtuelle Java.
La plupart des GC adoptent une méthode de nettoyage adaptative (ainsi que d'autres techniques supplémentaires pour améliorer la vitesse), principalement basées sur la recherche d'objets "en direct", puis à l'aide d'un collecteur de déchets "adaptatif, générationnel, stop-copy, mark-clean". Je ne présenterai pas trop, ce n'est pas l'objectif de cet article.
Fuite de mémoire en java
La fuite de mémoire en Java, largement et en termes de laïc, est que la mémoire des objets qui ne sera plus utilisée ne peut pas être recyclée, ce qui est une fuite de mémoire.
Les fuites de mémoire en Java sont différentes de celles de C ++.
En C ++, tous les objets qui ont été alloués à la mémoire doivent être libérés manuellement par le programmeur après qu'ils ne sont plus utilisés. Par conséquent, chaque classe contiendra un destructeur, qui consiste à terminer les travaux de nettoyage. Si nous oublions de libérer certains objets, cela entraînera une fuite de mémoire.
Mais en Java, nous n'avons pas besoin (et ne pouvons pas) libérer de la mémoire par nous-mêmes, et les objets inutiles sont automatiquement nettoyés par GC, ce qui simplifie considérablement notre travail de programmation. Cependant, parfois certains objets qui ne seront plus utilisés ne peuvent pas être libérés dans le GC, ce qui entraînera une fuite de mémoire.
Nous savons que les objets ont des cycles de vie, certains sont longs et certains sont courts. Si les objets du cycle de vie longues contiennent des références avec des cycles de vie courts, les fuites de mémoire sont probables. Donnons un exemple simple:
classe publique Simple {objet objet; public void method1 () {object = new object (); //...Other Code}}En fait, nous nous attendons à ce qu'il ne soit utilisé que dans la méthode Method1 () et qu'il ne sera pas utilisé ailleurs. Cependant, lorsque la méthode Method1 () est exécutée, la mémoire allouée par l'objet objet ne sera pas immédiatement considérée comme un objet qui peut être libéré et ne sera libéré qu'après que l'objet créé par la classe simple est libéré. À strictement parler, c'est une fuite de mémoire. La solution consiste à utiliser l'objet comme variable locale dans la méthode Method1 (). Bien sûr, si vous devez écrire ceci, vous pouvez le changer pour cela:
classe publique Simple {objet objet; public void method1 () {object = new object (); //...Other code object = null; }}De cette façon, la mémoire allouée par "newObject ()" peut être recyclée par GC.
À ce stade, les fuites de mémoire Java doivent être plus claires. Expliquons plus loin ci-dessous:
Lorsque la mémoire allouée dans le tas n'est pas libérée, toutes les façons d'accéder à cette mémoire sont supprimées (comme le réaffectation du pointeur). Ceci est pour des langues telles que C ++. Le GC en Java nous aidera à gérer cette situation, nous n'avons donc pas besoin de nous en soucier.
Lorsque l'objet mémoire n'est évidemment pas nécessaire, il conserve toujours cette mémoire et sa méthode d'accès (référence) qui est une fuite de mémoire qui peut se produire dans toutes les langues. Si vous ne faites pas attention lors de la programmation, cela est facile à se produire, et si ce n'est pas trop grave, ce n'est peut-être qu'une brève fuite de mémoire.
Quelques exemples et solutions sujets aux fuites de la mémoire
La situation comme l'exemple ci-dessus est facile à produire, et c'est aussi la situation la plus probable que nous ignorons et provoquons des fuites de mémoire. La solution consiste à minimiser la portée de l'objet (par exemple, dans AndroidStudio, le code ci-dessus émettra un avertissement, et les suggestions données sont de réécrire les variables membre de la classe en variables locales dans la méthode) et de définir manuellement la valeur nulle.
Quant à la portée, nous devons prêter plus d'attention lors de l'écriture de code; En définissant manuellement la valeur nul, nous pouvons jeter un œil à la méthode interne pour supprimer le nœud spécifié dans le code source LinkedList de conteneur Java (voir: Interprétation du code source LinkedList de Java (JDK1.8) ):
// Supprimez le nœud spécifié et renvoyez la valeur d'élément supprimée E Unlink (nœud <e> x) {// Obtenez la valeur actuelle et les nœuds avant et arrière final e élément e = x.item; Node final <e> next = x.next; Node final <e> prev = x.prev; if (prev == null) {premier = suivant; // Si le nœud précédent est vide (tel que le nœud actuel est le premier nœud), le nœud suivant devient le nouveau premier nœud} else {prev.next = next; // Si le nœud précédent n'est pas vide, alors il pointe vers le nœud suivant actuel x.prev = null; } if (next == null) {Last = prev; // Si le nœud suivant est vide (tel que le nœud actuel est un nœud de queue), le précédent du nœud actuel devient le nouveau nœud de queue} else {next.prev = prev; // Si le nœud suivant n'est pas vide, le nœud suivant pointe vers l'avant vers le nœud précédent actuel x.next = null; } x.item = null; taille--; modCount ++; élément de retour; }En plus de modifier la relation entre les nœuds, ce que nous devons également faire est d'attribuer une valeur à NULL. Peu importe quand GC commencera à nettoyer, nous devons marquer des objets inutiles comme des objets nettoyables dans le temps.
Nous savons que le Java Container ArrayList est implémenté dans un tableau (reportez-vous à: Interprétation du code source ArrayList de Java (JDK1.8) ). Si nous voulons écrire une méthode pop () (pop) pour cela, cela pourrait ressembler à ceci:
public e pop () {if (size == 0) return null; else return (e) elementData [- taille]; }La méthode d'écriture est très concise, mais elle provoquera un débordement de mémoire ici: ElementData [size-1] détient toujours une référence à un objet de type E et ne peut pas être recyclée par GC pour le moment. Nous pouvons le modifier comme suit:
public e pop () {if (size == 0) return null; else {e e = (e) elementData [- taille]; elementData [size] = null; retour e; }}Lors de l'écriture de code, nous ne pouvons pas poursuivre aveuglément la simplicité. La première chose est d'assurer sa précision.
Les fuites de mémoire pendant l'utilisation du conteneur
Dans de nombreux articles, vous pouvez voir un exemple de fuites de mémoire comme suit:
Vecteur v = nouveau vecteur (); pour (int i = 1; i <100; i ++) {objet o = nouveau objet (); v.add (o); o = null; }Beaucoup de gens peuvent ne pas le comprendre au début, nous pouvons donc comprendre le code ci-dessus comme suit:
void méthode () {vector vector = new vector (); pour (int i = 1; i <100; i ++) {objet objet = nouveau objet (); vector.add (objet); objet = null; } //...Operations sur vecteur // ... Autres opérations qui ne sont pas liées au vecteur}Ici, les fuites de mémoire se réfèrent au fait qu'après la fin de l'opération vectorielle, si une opération GC se produit, cette série d'objets ne peut pas être recyclée. La fuite de mémoire ici peut être de courte durée, car une fois la méthode entière de la méthode (), ces objets peuvent toujours être recyclés. Il est très simple de résoudre ici, attribuez simplement manuellement la valeur à NULL:
void méthode () {vector vector = new vector (); pour (int i = 1; i <100; i ++) {objet objet = nouveau objet (); vector.add (objet); objet = null; } //...Operations sur v vector = null; //... Autres opérations qui ne sont pas liées à V}Le vecteur ci-dessus est obsolète, mais ce n'est qu'une introduction aux fuites de mémoire en utilisant d'anciens exemples. Lorsque nous utilisons des conteneurs, il est facile de provoquer une fuite de mémoire, tout comme l'exemple ci-dessus. Cependant, dans l'exemple ci-dessus, l'effet de fuite de mémoire causé par les variables locales dans la méthode pendant le temps du conteneur peut ne pas être très grande (mais nous devons également l'éviter). Cependant, si ce conteneur est une variable de membre d'une classe, ou même une variable de membre statique, vous devez accorder plus d'attention à la fuite de la mémoire.
Ce qui suit est une erreur qui peut se produire lors de l'utilisation des conteneurs:
classe publique CollectionMemory {public static void main (String s []) {set <MyObject> objets = new LinkedHashSet <MyObject> (); objets.add (new myObject ()); objets.add (new myObject ()); objets.add (new myObject ()); objets.add (new myObject ()); System.out.println (objets.Size ()); while (true) {objets.add (new myObject ()); }}} class MyObject {// Définissez la longueur par défaut du tableau sur 99999 et se produit plus rapide outofMemoryError List <string> list = new ArrayList <> (99999);}L'exécution du code ci-dessus rapportera une erreur très rapidement:
3Exception dans le thread "Main" java.lang.outofMemoryError: Java Heap Space sur java.util.arraylist. <Init> (ArrayList.java:152) sur com.anxpp.memory.myobject. <Init> (CollectionMemory.java:21) à AT à la com.anxpp.memory.collectionmemory.main (CollectionMemory.java:16)
Si vous en savez suffisamment sur les conteneurs Java, l'erreur ci-dessus ne se produira pas. Voici également un article que je présente des conteneurs Java: ...
L'ensemble de conteneurs ne stocke que des éléments uniques et est comparé par la méthode equals () de l'objet. Cependant, toutes les classes de Java sont directement ou indirectement héritées de la classe d'objets. La méthode equals () de la classe d'objet compare l'adresse de l'objet. Dans l'exemple ci-dessus, des éléments seront ajoutés jusqu'à ce que la mémoire déborde de la mémoire.
Par conséquent, l'exemple ci-dessus est à proprement parler, un débordement de mémoire causé par la mauvaise utilisation du conteneur.
En ce qui concerne l'ensemble, la méthode supprime () utilise également la méthode equals () pour supprimer les éléments correspondants. Si un objet fournit la méthode equals () correcte, n'oubliez pas de ne pas utiliser Suppriter (Objecto) après avoir modifié cet objet, cela peut également provoquer une fuite de mémoire.
Divers objets qui fournissent une méthode close ()
Par exemple, lorsque vous utilisez des connexions de base de données (DataSoue.GetConnection ()), les connexions réseau (socket) et les connexions IO, et lors de l'utilisation d'autres cadres, ils ne seront pas automatiquement recyclés par GC, sauf s'ils appellent explicitement leur méthode close () (ou la méthode similaire) pour fermer leur connexion. En fait, la raison en est que les objets de cycle de longue durée détiennent des références aux objets de cycle de courte durée.
Beaucoup de gens peuvent avoir utilisé l'hibernate. Lorsque nous exploitons la base de données, nous obtenons une session via le SessionFactory:
Session Session = SessionFactory.OpenSession ();
Une fois terminé, nous devons appeler la méthode Close () pour fermer:
session.close ();
SessionFactory est un objet à longue durée de vie, et la session est un objet relativement court-circuit, mais le cadre est conçu de cette manière: il ne sait pas combien de temps nous utiliserons la session, donc il ne peut que nous fournir un moyen de décider quand nous ne l'utiliserons plus.
Parce qu'une exception peut être lancée avant que la méthode Close () ne soit appelée, la méthode ne peut donc pas être appelée. Nous utilisons généralement la langue d'essai, puis nous exécutons enfin close () et autres travaux de nettoyage dans l'instruction:
try {session = sessionfactory.opencession (); //...Other opérations} enfin {session.close (); }Les fuites de mémoire causées par le mode singleton
Dans de nombreux cas, nous pouvons considérer son cycle de vie comme similaire au cycle de vie de l'ensemble du programme, c'est donc un objet avec un long cycle de vie. Si cet objet contient des références à d'autres objets, les fuites de mémoire sont également susceptibles de se produire.
Références aux classes internes et aux modules externes
En fait, le principe est toujours le même, mais la façon dont elle apparaît est différente.
Méthodes liées au nettoyage
Cette section parle principalement des méthodes GC () et Finalize ().
GC ()
Pour les programmeurs, GC est fondamentalement transparent et invisible. La fonction qui exécute GC est System.gc (), et après l'avoir appelé, il démarre le collecteur des ordures et commence à nettoyer.
Cependant, selon la définition de la spécification du langage Java, cette fonction ne garantit pas que le collecteur de déchets 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 hotspotjvm fourni par Sun prend en charge cette fonctionnalité.
finaliser()
finalize () est une méthode dans la classe d'objets.
Ceux qui savent C ++ savent qu'il y a un destructeur, mais notez que finaliser () n'est en aucun cas égal au destructeur en C ++.
Ceci est expliqué dans l'idée de programmation Java: une fois que GC est prêt à libérer l'espace de stockage occupé par l'objet, sa méthode finaliser () sera appelée en premier, et la mémoire occupée par l'objet ne sera recyclé que lorsque la prochaine action de recyclage GC se produira, donc nous pouvons mettre un travail de nettoyage dans Finalize ().
Un objectif important de cette méthode est: lors de l'appel du code non java (tel que C et C ++) dans Java, les opérations appliquées par la mémoire correspondantes (telles que la fonction malloc () de C) peuvent être utilisées dans ces codes non java, et dans ces codes non java, ces souvenirs ne publient pas efficacement ces souvenirs, vous pouvez utiliser la méthode finalisée () et appeler la méthode locale libre () et d'autres fonctions.
Par conséquent, finaliser () ne convient pas au nettoyage ordinaire.
Cependant, parfois, cette méthode a quelques utilisations:
S'il y a une série d'objets, l'un des objets a un état de faux. Si nous avons traité cet objet, l'état deviendra vrai. Afin d'éviter les objets manquants sans traitement, vous pouvez utiliser la méthode finaliser ():
class myObject {booléen state = false; public void deal () {//...some opérations de traitement State = true; } @Override Protected void finalize () {if (! State) {System.out.println ("Erreur:" + "Objet non traité!"); }} // ...}Mais à partir de nombreux aspects, cette méthode est recommandée pour ne pas être utilisée et considérée comme redondante.
En général, les fuites de mémoire sont causées par un mauvais codage. Nous ne pouvons pas blâmer le JVM de ne pas nettoyer plus raisonnablement.
Résumer
Ce qui précède est toute l'explication détaillée du code divulgué de la mémoire dans la langue java. J'espère que ce sera utile à tout le monde. Les amis intéressés peuvent continuer à se référer à d'autres sujets connexes sur ce site. S'il y a des lacunes, veuillez laisser un message pour le signaler. Merci vos amis pour votre soutien pour ce site!