web-dev-qa-db-fra.com

Création d'une fuite de mémoire avec Java

Je viens d'avoir une interview et on m'a demandé de créer une fuite mémoire avec Java.
Inutile de dire que je me sentais plutôt stupide, ne sachant pas comment commencer à en créer un.

Que serait un exemple?

3018
Mat B.

Voici un bon moyen de créer une véritable fuite de mémoire (objets inaccessibles en exécutant du code mais toujours stockés en mémoire) en Java pur:

  1. L'application crée un thread de longue durée (ou utilisez un pool de threads pour générer des fuites encore plus rapidement).
  2. Le thread charge une classe via un ClassLoader (éventuellement personnalisé).
  3. La classe alloue une grande quantité de mémoire (par exemple, new byte[1000000]), stocke une référence forte à elle dans un champ statique, puis stocke une référence à elle-même dans un ThreadLocal. L'affectation de la mémoire supplémentaire est facultative (la fuite de l'instance de classe suffit), mais la fuite fonctionnera beaucoup plus rapidement.
  4. Le thread efface toutes les références à la classe personnalisée ou au ClassLoader à partir duquel elle a été chargée.
  5. Répéter.

Cela fonctionne car le ThreadLocal conserve une référence à l'objet, ce qui conserve une référence à sa classe, qui à son tour conserve une référence à son classLoader. Le ClassLoader, à son tour, conserve une référence à toutes les classes qu'il a chargées.

(C’était pire dans de nombreuses implémentations de machine virtuelle, en particulier avant Java 7, car Classes et ClassLoaders étaient alloués directement dans permgen et n’étaient jamais du tout déclarés. Cependant, quelle que soit la façon dont la machine virtuelle gère le déchargement de classe, un ThreadLocal empêchera toujours la récupération d'un objet de classe.)

Une variante de ce modèle est la raison pour laquelle les conteneurs d'applications (tels que Tomcat) peuvent perdre de la mémoire comme un tamis si vous redéployez fréquemment des applications utilisant de manière quelconque ThreadLocals. (Étant donné que le conteneur d'applications utilise les threads comme décrit et que vous redéployez l'application, un nouveau ClassLoader est utilisé.)

Mise à jour : Étant donné que de nombreuses personnes ne cessent de le demander, voici un exemple de code montrant ce comportement en action .

2186
Daniel Pryden

Champ statique contenant la référence d'objet [esp final field]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

Appel String.intern() sur une longue chaîne

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(non clos) flux ouverts (fichier, réseau etc ...)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Connexions non fermées

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Zones inaccessibles depuis le récupérateur de mémoire de la machine virtuelle Java , telles que la mémoire allouée par le biais de méthodes natives

Dans les applications Web, certains objets sont stockés dans le champ d'application jusqu'à ce que l'application soit explicitement arrêtée ou supprimée.

getServletContext().setAttribute("SOME_MAP", map);

Options JVM incorrectes ou inappropriées , telles que l'option noclassgc sur le JDK IBM qui empêche le nettoyage de la classe inutilisé

Voir paramètres IBM jdk .

1167
Prashant Bhate

Une chose simple à faire est d'utiliser un HashSet avec un hashCode() ou equals() incorrect (ou inexistant), puis de continuer à ajouter des "doublons". Au lieu d'ignorer les doublons comme il se doit, l'ensemble ne fera que grandir et vous ne pourrez pas les supprimer.

Si vous voulez que ces mauvaises clés/éléments traînent, vous pouvez utiliser un champ statique comme

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}

Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
441
Peter Lawrey

Ci-dessous se trouve un cas non évident où Java fuit, outre le cas standard des auditeurs oubliés, des références statiques, des clés fictives/modifiables dans hashmaps, ou simplement des threads bloqués sans aucune chance de mettre fin à leur cycle de vie. .

  • File.deleteOnExit() - laisse toujours passer la chaîne, si la chaîne est une sous-chaîne, la fuite est encore pire (le caractère sous-jacent [] est également divulgué) - dans Java 7, la sous-chaîne copie également le char[], de sorte que ce dernier ne s'applique pas; @ Daniel, pas besoin de votes, cependant.

Je vais me concentrer sur les discussions pour montrer le danger que représentent les discussions non gérées, sans même vouloir toucher au swing.

  • Runtime.addShutdownHook et ne pas supprimer ... et même, même avec removeShutdownHook en raison d'un bogue dans la classe ThreadGroup concernant les threads non démarrés, il peut ne pas être collecté, fuit efficacement le ThreadGroup. JGroup a la fuite dans GossipRouter.

  • Créer, mais pas commencer, une Thread rentre dans la même catégorie que ci-dessus.

  • Créer un fil hérite des ContextClassLoader et AccessControlContext, plus du ThreadGroup et de tout InheritedThreadLocal, toutes ces références sont des fuites potentielles, ainsi que toutes les classes chargées par le chargeur de classes et tous références statiques et ja-ja. Cet effet est particulièrement visible dans l’ensemble du framework j.u.c.Executor, qui dispose d’une interface très simple ThreadFactory, mais la plupart des développeurs n’ont aucune idée du danger à venir. De plus, de nombreuses bibliothèques démarrent des threads sur demande (beaucoup trop de bibliothèques populaires dans l’industrie).

  • ThreadLocal caches; ceux-ci sont diaboliques dans de nombreux cas. Je suis sûr que tout le monde a vu un peu de caches simples basés sur ThreadLocal, et bien la mauvaise nouvelle: si le fil continue plus que prévu, la vie dans le contexte ClassLoader, c'est une pure petite fuite de Nice. N'utilisez pas de caches ThreadLocal sauf si cela est vraiment nécessaire.

  • Appel de ThreadGroup.destroy() lorsque le ThreadGroup n'a pas de thread lui-même, mais qu'il conserve toujours des ThreadGroups enfants. Mauvaise fuite qui empêchera le ThreadGroup de disparaître de son parent, mais tous les enfants ne pourront plus être énumérés.

  • L'utilisation de WeakHashMap et de la valeur (in) référence directement la clé. C’est difficile à trouver sans fichier mémoire. Ceci s’applique à tous les Weak/SoftReference étendus qui pourraient conserver une référence dure à l’objet gardé.

  • Utilisation de Java.net.URL avec le protocole HTTP (S) et chargement de la ressource à partir de (!). Celui-ci est spécial, le KeepAliveCache crée un nouveau thread dans le système ThreadGroup qui laisse fuir le chargeur de classes de contexte du thread en cours. Le thread est créé à la première demande lorsqu'aucun thread vivant n'existe, vous pouvez donc avoir de la chance ou tout simplement fuir. La fuite est déjà corrigée dans Java 7 et le code qui crée le thread supprime correctement le chargeur de classes de contexte. Il y a peu de cas supplémentaires (comme ImageFetcher, également corrigé) de créer des threads similaires.

  • Utiliser InflaterInputStream passer new Java.util.Zip.Inflater() dans le constructeur (PNGImageDecoder par exemple) et ne pas appeler end() de l'inflateur. Eh bien, si vous passez dans le constructeur avec juste new, aucune chance ... Et oui, appeler close() sur le flux ne ferme pas l'inflater s'il est passé manuellement en tant que paramètre du constructeur. Ce n'est pas une vraie fuite car elle serait libérée par le finaliseur ... quand elle le jugera nécessaire. Jusqu'à ce moment-là, il consomme tellement de mémoire native que Linux oom_killer peut tuer le processus en toute impunité. Le problème principal est que la finalisation dans Java est très peu fiable et que G1 l'a aggravée jusqu'à la version 7.0.2. Morale de l'histoire: libérez les ressources natives dès que vous le pouvez; le finaliseur est trop pauvre.

  • Même cas avec Java.util.Zip.Deflater. Celui-ci est bien pire car Deflater a besoin de beaucoup de mémoire en Java, c’est-à-dire qu’il utilise toujours 15 bits (maximum) et 8 niveaux de mémoire (9 au maximum), allouant plusieurs centaines de Ko de mémoire native. Heureusement, Deflater n'est pas largement utilisé et, à ma connaissance, JDK ne contient aucun abus. Appelez toujours end() si vous créez manuellement un Deflater ou Inflater. La meilleure partie des deux derniers: vous ne pouvez pas les trouver via les outils de profilage normaux disponibles.

(Je peux ajouter quelques pertes de temps que j'ai rencontrées à la demande.)

Bonne chance et rester en sécurité; les fuites sont mauvaises!

262
bestsss

La plupart des exemples ici sont "trop ​​complexes". Ce sont des cas Edge. Avec ces exemples, le programmeur a commis une erreur (comme ne redéfinissant pas equals/hashcode), ou a été mordu par un cas de la JVM/Java (chargement de classe avec static ...). Je pense que ce n'est pas le type d'exemple qu'un intervieweur veut ou même le cas le plus courant.

Mais il existe des cas vraiment plus simples pour les fuites de mémoire. Le ramasse-miettes ne libère que ce qui n'est plus référencé. En tant que développeurs Java, nous ne nous soucions pas de la mémoire. Nous l'attribuons en cas de besoin et laissons le libérer automatiquement. Bien.

Mais toute application de longue durée a tendance à avoir un état partagé. Cela peut être n'importe quoi, la statique, les singletons ... Souvent, les applications non triviales ont tendance à créer des graphes d'objets complexes. Le simple fait d'oublier de définir une référence sur null ou plus souvent d'oublier de supprimer un objet d'une collection suffit à provoquer une fuite de mémoire.

Bien entendu, tous les types d'écouteurs (comme ceux de l'interface utilisateur), les caches ou tout état partagé de longue durée ont tendance à produire une fuite de mémoire s'ils ne sont pas gérés correctement. Ce qu'il faut comprendre, c'est qu'il ne s'agit pas d'un cas d'angle Java, ni d'un problème avec le garbage collector. C'est un problème de conception. Nous concevons que nous ajoutons un auditeur à un objet de longue durée, mais nous ne supprimons pas l'auditeur lorsqu'il n'est plus nécessaire. Nous mettons en cache des objets, mais nous n'avons aucune stratégie pour les supprimer du cache.

Nous avons peut-être un graphique complexe qui stocke l'état précédent requis par un calcul. Mais l'état précédent est lui-même lié à l'état d'avant et ainsi de suite.

Comme nous devons fermer des connexions ou des fichiers SQL. Nous devons définir les références appropriées sur null et supprimer des éléments de la collection. Nous aurons des stratégies de cache appropriées (taille maximale de la mémoire, nombre d'éléments ou minuteries). Tous les objets permettant à un auditeur d'être notifié doivent fournir une méthode addListener et une méthode removeListener. Et lorsque ces notificateurs ne sont plus utilisés, ils doivent effacer leur liste d'écouteurs.

Une fuite de mémoire est en effet vraiment possible et parfaitement prévisible. Pas besoin de fonctionnalités linguistiques spéciales ou de cas d'angle. Les fuites de mémoire sont soit un signe de manque, soit des problèmes de conception.

189
Nicolas Bousquet

La réponse dépend entièrement de ce que l'intervieweur pensait demander.

Est-il possible en pratique de faire fuir Java? Bien sûr que oui, et il existe de nombreux exemples dans les autres réponses.

Mais il y a plusieurs méta-questions qui peuvent avoir été posées?

  • Une implémentation Java théoriquement "parfaite" est-elle vulnérable aux fuites?
  • Le candidat comprend-il la différence entre théorie et réalité?
  • Le candidat comprend-il le fonctionnement de la collecte des ordures?
  • Ou comment la collecte des ordures est-elle censée fonctionner dans un cas idéal?
  • Savent-ils qu'ils peuvent appeler d'autres langues via des interfaces natives?
  • Savent-ils qu'ils perdent de la mémoire dans ces autres langues?
  • Le candidat sait-il même ce qu'est la gestion de la mémoire et ce qui se passe dans les coulisses de Java?

Je lis votre méta-question en tant que "Quelle réponse j'aurais pu utiliser dans cette situation d'entrevue". Et par conséquent, je vais me concentrer sur les compétences en entretien plutôt qu'en Java. Je crois que vous êtes plus susceptible de répéter la situation consistant à ne pas connaître la réponse à une question posée dans une interview plutôt que de devoir savoir comment faire fuir Java. Donc, espérons-le, cela aidera.

L'une des compétences les plus importantes que vous puissiez développer pour un entretien consiste à apprendre à écouter activement les questions et à travailler avec l'intervieweur pour en dégager le but. Cela vous permet non seulement de répondre à leur question comme ils le souhaitent, mais montre également que vous possédez des compétences de communication vitales. Et quand il s'agit de choisir entre de nombreux développeurs tout aussi talentueux, j'engage celui qui écoute, qui pense et comprend avant de répondre à chaque fois.

152
PlayTank

Ce qui suit est un exemple assez inutile, si vous ne comprenez pas JDBC . Ou du moins comment JDBC attend un développeur qu'il ferme les instances Connection, Statement et ResultSet avant de les supprimer ou de perdre des références, au lieu de s'appuyer sur la mise en oeuvre de finalize.

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}

Le problème avec ce qui précède est que l’objet Connection n’est pas fermé, et donc la connexion physique reste ouverte, jusqu’à ce que le ramasse-miettes se présente et constate qu’il est inaccessible. GC invoquera la méthode finalize, mais il existe des pilotes JDBC qui n'implémentent pas finalize, du moins pas de la même manière que Connection.close est implémenté. Le comportement qui en résulte est que, même si la mémoire sera récupérée en raison de la collecte d'objets inaccessibles, les ressources (y compris la mémoire) associées à l'objet Connection risquent tout simplement de ne pas être récupérées.

Dans un tel cas où la méthode Connection's finalize ne nettoie pas tout, il est possible que la connexion physique au serveur de base de données dure plusieurs cycles de récupération de place, jusqu'à ce que le serveur de base de données comprenne finalement que la connexion n'est pas vivante (si c'est le cas) et doit être fermée.

Même si le pilote JDBC devait implémenter finalize, il est possible que des exceptions soient générées lors de la finalisation. Il en résulte que toute mémoire associée à l'objet désormais "dormant" ne sera pas récupérée, car il est garanti que finalize ne sera invoqué qu'une seule fois.

Le scénario ci-dessus consistant à rencontrer des exceptions lors de la finalisation d'un objet est lié à un autre scénario pouvant éventuellement conduire à une fuite de mémoire: la résurrection d'objet. La résurrection d'objet est souvent faite intentionnellement en créant une forte référence à l'objet en cours de finalisation, à partir d'un autre objet. Lorsque la résurrection d'objet est mal utilisée, une fuite de mémoire est associée à d'autres sources de fuites de mémoire.

Il y a beaucoup plus d'exemples que vous pouvez évoquer - comme

  • Gérer une instance List où vous ne faites qu'ajouter à la liste et ne pas la supprimer (bien que vous deviez vous débarrasser des éléments dont vous n'avez plus besoin), ou
  • Ouvrir Sockets ou Files, mais ne pas les fermer quand ils ne sont plus nécessaires (comme dans l'exemple ci-dessus impliquant la classe Connection.).
  • Ne pas décharger des singletons lors de la fermeture d'une application Java EE. Apparemment, le chargeur de classe qui a chargé la classe singleton conservera une référence à la classe et, par conséquent, l'instance singleton ne sera jamais collectée. Lorsqu'une nouvelle instance de l'application est déployée, un nouveau chargeur de classes est généralement créé et l'ancien chargeur de classes continuera d'exister en raison du singleton.
127
Vineet Reynolds

L’implémentation de ArrayList.remove (int) est probablement l’un des exemples les plus simples de fuite de mémoire potentielle et de la façon de l’éviter:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

Si vous l'implémentiez vous-même, auriez-vous pensé à effacer l'élément de tableau qui n'est plus utilisé (elementData[--size] = null)? Cette référence pourrait garder un objet énorme en vie ...

115
meriton

Chaque fois que vous gardez des références à des objets dont vous n’avez plus besoin, vous avez une fuite de mémoire. Voir Traitement des fuites de mémoire dans les programmes Java pour consulter des exemples montrant comment les fuites de mémoire se manifestent dans Java et ce que vous pouvez faire à ce sujet.

65
Bill the Lizard

Vous pouvez créer une fuite de mémoire avec la classe Sun.misc.Unsafe. En fait, cette classe de service est utilisée dans différentes classes standard (par exemple, dans les classes Java.nio). Vous ne pouvez pas créer d'instance de cette classe directement, mais vous pouvez tiliser la réflexion pour le faire.

Le code ne se compile pas dans Eclipse IDE - compilez-le à l'aide de la commande javac (vous obtiendrez des avertissements lors de la compilation)

import Java.lang.reflect.Constructor;
import Java.lang.reflect.Field;
import Sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("Sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}
49
stemm

Je peux copier ma réponse à partir d’ici: Le moyen le plus simple de provoquer une fuite de mémoire en Java?

"En informatique, une fuite de mémoire (ou une fuite, dans ce contexte) se produit lorsqu'un programme informatique consomme de la mémoire mais ne peut pas la restituer au système d'exploitation." (Wikipédia)

La réponse facile est: tu ne peux pas. Java gère automatiquement la mémoire et libère des ressources inutiles. Vous ne pouvez pas empêcher cela de se produire. Il sera TOUJOURS capable de libérer les ressources. Dans les programmes avec gestion manuelle de la mémoire, cela est différent. Vous pouvez obtenir de la mémoire en C en utilisant malloc (). Pour libérer la mémoire, vous avez besoin du pointeur renvoyé par malloc et appelez free () dessus. Mais si vous n'avez plus le pointeur (écrasé, ou la durée de vie dépassée), alors vous êtes malheureusement incapable de libérer cette mémoire et vous avez donc une fuite de mémoire.

Toutes les autres réponses à ce jour sont dans ma définition pas vraiment des fuites de mémoire. Ils visent tous à remplir la mémoire avec des trucs inutiles très rapidement. Mais à tout moment, vous pouvez toujours déréférencer les objets que vous avez créés et libérer ainsi la mémoire -> AUCUNE FUITE. réponse d'Acconrad est assez proche, mais je dois admettre que sa solution consiste à "écraser" le récupérateur de mémoire en le forçant dans une boucle sans fin).

La réponse longue est la suivante: Vous pouvez obtenir une fuite de mémoire en écrivant une bibliothèque pour Java à l'aide de l'interface JNI, qui peut gérer manuellement la mémoire et avoir ainsi des fuites de mémoire. Si vous appelez cette bibliothèque, votre processus Java perdra de la mémoire. Ou bien, vous pouvez avoir des bogues dans la machine virtuelle Java, de sorte que celle-ci perde de la mémoire. Il y a probablement des bogues dans la machine virtuelle, voire même des problèmes connus, car la récupération de place n'est pas si simple, mais c'est quand même un bogue. Par conception, ce n'est pas possible. Vous demandez peut-être du code Java affecté par un tel bogue. Désolé, je n'en connais pas et il est fort possible que ce ne soit plus un bogue dans la prochaine version Java.

43
yankee

Voici un simple/sinistre via http://wiki.Eclipse.org/Performance_Bloopers#String.substring.28.29 .

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

Étant donné que la sous-chaîne fait référence à la représentation interne de la chaîne d'origine, beaucoup plus longue, l'original reste en mémoire. Ainsi, tant que vous avez un StringLeaker en jeu, vous avez également la chaîne entière en mémoire, même si vous pouvez penser que vous ne tenez que sur une chaîne à un caractère.

Pour éviter de stocker une référence indésirable à la chaîne d'origine, procédez comme suit:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

Pour plus de mal, vous pouvez aussi .intern() la sous-chaîne:

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

Cela permettra de conserver en mémoire la chaîne longue d'origine et la sous-chaîne dérivée, même après la suppression de l'instance de StringLeaker.

36
Jon Chambers

Prenez n'importe quelle application Web s'exécutant dans n'importe quel conteneur de servlets (Tomcat, Jetty, Glassfish, peu importe ...). Redéployez l'application 10 ou 20 fois de suite (il suffit parfois de toucher le fichier WAR dans le répertoire de déploiement automatique du serveur.

À moins que quelqu'un n'ait réellement testé cela, il y a de fortes chances que vous obteniez une erreur OutOfMemoryError après quelques redéploiements, car l'application n'a pas pris soin de se nettoyer d'elle-même. Vous pouvez même trouver un bogue sur votre serveur avec ce test.

Le problème est que la durée de vie du conteneur est plus longue que celle de votre application. Vous devez vous assurer que toutes les références du conteneur aux objets ou aux classes de votre application peuvent être nettoyées.

Si une seule référence survit au non déploiement de votre application Web, le chargeur de classes correspondant et, par conséquent, toutes les classes de votre application Web ne peuvent pas être nettoyés.

Les threads lancés par votre application, les variables ThreadLocal, l'enregistrement des ajouts sont quelques-uns des suspects habituels à l'origine des fuites du chargeur de classes.

35
Harald Wellmann

Un exemple courant de ceci dans le code d'interface graphique est lors de la création d'un widget/composant et de l'ajout d'un écouteur à un objet couvert statique/application et de la suppression de l'écouteur lorsque le widget est détruit. Vous obtenez non seulement une fuite de mémoire, mais également un coup dur pour les performances, car lorsque vous écoutez des événements, tous vos anciens auditeurs sont également appelés.

35
pauli

Peut-être en utilisant du code natif externe via JNI?

Avec Java pur, c'est presque impossible.

Mais il s’agit d’un type de fuite de mémoire "standard", lorsque vous ne pouvez plus accéder à la mémoire, mais qu’elle appartient toujours à l’application. Vous pouvez plutôt conserver des références à des objets inutilisés ou ouvrir des flux sans les fermer par la suite.

33
Rogach

J'ai eu une "fuite de mémoire" de Nice en relation avec PermGen et l'analyse XML une fois. L'analyseur XML que nous avons utilisé (je ne me souviens plus lequel) était un String.intern () sur les noms de balises pour accélérer la comparaison. Un de nos clients a eu la bonne idée de stocker des valeurs de données non pas dans des attributs XML ni dans du texte, mais sous forme de noms de variables. Nous avions donc un document du type:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

En fait, ils n'utilisaient pas de chiffres, mais des identifiants textuels plus longs (environ 20 caractères), uniques et enregistrés à un rythme de 10 à 15 millions par jour. Cela fait 200 Mo de déchets par jour, ce qui n’est plus jamais nécessaire et qui n’a jamais été transféré (car il est en PermGen). Permgen étant réglé sur 512 Mo, il a fallu environ deux jours pour que l'exception de mémoire insuffisante (OOME) arrive ...

29
Ron

Qu'est-ce qu'une fuite de mémoire?

  • Elle est causée par un bug ou mauvaise conception.
  • C'est un gaspillage de mémoire.
  • La situation empire avec le temps.
  • Le ramasse-miettes ne peut pas le nettoyer.

Exemple typique:

Un cache d'objets est un bon point de départ pour tout gâcher.

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

Votre cache grandit et grandit. Et très vite, toute la base de données est aspirée en mémoire. Une meilleure conception utilise un LRUMap (conserve uniquement les objets récemment utilisés dans le cache).

Bien sûr, vous pouvez rendre les choses beaucoup plus compliquées:

  • en utilisant ThreadLocal constructions.
  • ajout de plus arbres de référence complexes.
  • ou des fuites causées par bibliothèques tierces.

Qu'est-ce qui se passe souvent:

Si cet objet Info comporte des références à d'autres objets, ceux-ci le sont également. D'une certaine manière, vous pourriez aussi considérer qu'il s'agit d'une fuite de mémoire (due à une mauvaise conception).

23
bvdb

J'ai récemment rencontré une situation de fuite de mémoire causée d'une manière par log4j.

Log4j a ce mécanisme appelé Contexte de diagnostic imbriqué (NDC) , qui est un instrument permettant de distinguer la sortie de journal entrelacée de différentes sources. La granularité à laquelle le NDC fonctionne est les threads, ce qui permet de distinguer les sorties de journal de différents threads séparément.

Afin de stocker les balises spécifiques aux threads, la classe NDC de log4j utilise une table de hachage qui est indexée par l'objet Thread lui-même (contrairement à l'ID de thread), et jusqu'à ce que la balise NDC reste en mémoire avec tous les objets qui pendent du thread. l'objet reste aussi en mémoire. Dans notre application Web, nous utilisons NDC pour baliser les sorties de connexion avec un ID de demande afin de distinguer les journaux d'une seule demande séparément. Le conteneur qui associe la balise NDC à un thread, la supprime également tout en renvoyant la réponse d'une requête. Le problème est survenu lors du traitement d'une demande, un thread enfant a été créé, quelque chose comme le code suivant:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

Ainsi, un contexte NDC a été associé à un thread en ligne qui a été généré. L'objet thread qui était la clé de ce contexte NDC est le thread en ligne auquel l'objet énormeList est suspendu. Par conséquent, même après que le thread ait fini de faire ce qu'il faisait, la référence à la liste énorme a été maintenue en vie par le contexte NDC Hastable, provoquant ainsi une fuite de mémoire.

22
Puneet

J'ai trouvé intéressant de noter que personne n'a utilisé les exemples de classe internes. Si vous avez une classe interne; il maintient intrinsèquement une référence à la classe contenante. Bien entendu, il ne s’agit techniquement pas d’une fuite de mémoire, car Java finira par le nettoyer. mais cela peut amener les classes à rester plus longtemps que prévu.

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Maintenant, si vous appelez Exemple1 et obtenez un Exemple2 éliminant Exemple1, vous aurez toujours un lien vers un objet Exemple1.

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

J'ai aussi entendu une rumeur disant que si vous avez une variable qui existe depuis plus longtemps, Java suppose qu'il existera toujours et ne tentera jamais de le nettoyer s'il n'est plus possible d'atteindre le code. Mais c'est complètement non vérifié.

22
Suroot

L'intervieweur cherchait probablement une référence circulaire comme le code ci-dessous (qui ne contient d'ailleurs que de la mémoire dans de très anciennes machines virtuelles utilisant le comptage de références, ce qui n'est plus le cas). Mais comme la question est plutôt vague, il s’agit donc d’une excellente occasion de démontrer votre compréhension de la gestion de la mémoire JVM.

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

Ensuite, vous pouvez expliquer qu'avec le comptage de références, le code ci-dessus perdrait de la mémoire. Mais la plupart des machines virtuelles modernes n'utilisent plus le comptage de références, mais utilisent généralement un ramasse-miettes à balayage, qui collectera en fait cette mémoire.

Ensuite, vous expliquez comment créer un objet ayant une ressource native sous-jacente, comme ceci:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

Vous pouvez ensuite expliquer qu'il s'agit techniquement d'une fuite de mémoire, mais cette fuite est due au code natif de la machine virtuelle allouant des ressources natives sous-jacentes, qui n'ont pas été libérées par votre code Java.

En fin de compte, avec une machine virtuelle moderne, vous devez écrire du code Java qui alloue une ressource native en dehors de la portée normale de la connaissance de la machine virtuelle.

19
deltamind106

Créez une carte statique et continuez à y ajouter des références précises. Ceux-ci ne seront jamais GC'd.

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
18
duffymo

Tout le monde oublie toujours la route de code natif. Voici une formule simple pour une fuite:

  1. Déclarez la méthode native.
  2. Dans la méthode native, appelez malloc. N'appelez pas free.
  3. Appelez la méthode native.

N'oubliez pas que les allocations de mémoire en code natif proviennent du segment de mémoire JVM.

17
Paul Morie

Vous pouvez créer une fuite de mémoire en déplacement en créant une nouvelle instance d'une classe dans la méthode finalize de cette classe. Points bonus si le finaliseur crée plusieurs instances. Voici un programme simple qui divise tout le tas en quelques secondes à quelques minutes, en fonction de la taille de votre tas:

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}
16
sethobrien

Je ne pense pas que quiconque ait déjà dit cela: vous pouvez ressusciter un objet en redéfinissant la méthode finalize () de manière à ce que finalize () stocke une référence à cet endroit quelque part. Le ramasse-miettes ne sera appelé qu'une fois sur l'objet, après quoi l'objet ne sera jamais détruit.

15
Ben

Je suis récemment tombé sur un type de fuite de ressources plus subtil. Nous ouvrons des ressources via getResourceAsStream du chargeur de classe et il est arrivé que les descripteurs de flux d'entrée ne soient pas fermés.

Uhm, pourriez-vous dire, quel idiot.

Eh bien, ce qui rend cette question intéressante est la suivante: de cette manière, vous pouvez perdre de la mémoire du processus sous-jacent plutôt que de celle de la machine virtuelle Java.

Tout ce dont vous avez besoin est un fichier jar contenant un fichier à l'intérieur duquel sera référencé le code Java. Plus le fichier JAR est volumineux, plus la mémoire allouée est rapide.

Vous pouvez facilement créer un tel pot avec la classe suivante:

import Java.io.File;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.util.Zip.ZipEntry;
import Java.util.Zip.ZipOutputStream;

public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}

Collez simplement dans un fichier nommé BigJarCreator.Java, compilez-le et exécutez-le à partir de la ligne de commande:

javac BigJarCreator.Java
java -cp . BigJarCreator

Et voilà: vous trouvez une archive JAR dans votre répertoire de travail actuel avec deux fichiers à l'intérieur.

Créons une deuxième classe:

public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");

        Thread.sleep(Long.MAX_VALUE);
    }

}

Cette classe ne fait fondamentalement rien, mais crée des objets InputStream non référencés. Ces objets seront immédiatement récupérés et ne contribueront donc pas à la taille du tas. Il est important pour notre exemple de charger une ressource existante à partir d'un fichier jar, et la taille compte ici!

Si vous avez des doutes, essayez de compiler et de démarrer la classe ci-dessus, mais assurez-vous de choisir une taille de tas décente (2 Mo):

javac MemLeak.Java
java -Xmx2m -classpath .:big.jar MemLeak

Vous ne rencontrerez pas d'erreur de MOO ici, car aucune référence n'est conservée, l'application continuera à fonctionner quelle que soit la taille de l'option ITERATIONS dans l'exemple ci-dessus. La consommation de mémoire de votre processus (visible dans l'en-tête (RES/RSS) ou l'Explorateur de processus) augmente, sauf si l'application parvient à la commande wait. Dans la configuration ci-dessus, environ 150 Mo de mémoire seront alloués.

Si vous souhaitez que l'application soit sécurisée, fermez le flux d'entrée à l'endroit où il est créé:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

et votre processus ne dépassera pas 35 Mo, indépendamment du nombre d'itérations.

Assez simple et surprenant.

15
Jay

Comme beaucoup de gens l'ont suggéré, les fuites de ressources sont assez faciles à provoquer - comme dans les exemples JDBC. Les fuites de mémoire réelles sont un peu plus difficiles, en particulier si vous ne comptez pas sur des fragments cassés de la machine virtuelle Java pour le faire à votre place ...

Les idées de créer des objets qui ont une très grande empreinte et de ne pas pouvoir y accéder ne sont pas non plus de véritables fuites de mémoire. Si rien ne peut y accéder, les déchets seront collectés, et si quelque chose peut y accéder, il ne s'agit pas d'une fuite ...

Une façon pour tilisé de travailler - et je ne sais pas si c'est toujours le cas - est d’avoir une chaîne circulaire de trois profondeurs. Comme dans l'objet A une référence à l'objet B, l'objet B a une référence à l'objet C et l'objet C à une référence à l'objet A. Le GC était assez intelligent pour savoir qu'une chaîne de deux profondeurs - comme dans A <-> B - peut être collecté en toute sécurité si A et B ne sont pas accessibles par autre chose, mais ne peuvent pas gérer la chaîne à trois voies ...

14
Graham

il y a beaucoup de situations différentes, la mémoire va fuir. Un que j'ai rencontré, qui expose une carte qui ne devrait pas être exposée et utilisée à un autre endroit.

public class ServiceFactory {

private Map<String, Service> services;

private static ServiceFactory singleton;

private ServiceFactory() {
    services = new HashMap<String, Service>();
}

public static synchronized ServiceFactory getDefault() {

    if (singleton == null) {
        singleton = new ServiceFactory();
    }
    return singleton;
}

public void addService(String name, Service serv) {
    services.put(name, serv);
}

public void removeService(String name) {
    services.remove(name);
}

public Service getService(String name, Service serv) {
    return services.get(name);
}

// the problematic api, which expose the map.
//and user can do quite a lot of thing from this api.
//for example, create service reference and forget to dispose or set it null
//in all this is a dangerous api, and should not expose 
public Map<String, Service> getAllServices() {
    return services;
}

}

// resource class is a heavy class
class Service {

}
11
Ben Xu

Les threads ne sont pas collectés jusqu'à ce qu'ils se terminent. Ils servent comme racines du ramassage des ordures. Ils sont l’un des rares objets qui ne seront pas récupérés simplement en les oubliant ou en supprimant les références.

Considérez: le modèle de base pour terminer un thread de travail consiste à définir une variable de condition vue par le thread. Le thread peut vérifier la variable périodiquement et l'utiliser comme signal pour se terminer. Si la variable n'est pas déclarée volatile, alors la modification apportée à la variable risque de ne pas être vue par le thread, de sorte qu'il ne saura pas se terminer. Ou imaginez si certains threads souhaitent mettre à jour un objet partagé, mais bloquez-les en essayant de les verrouiller.

Si vous n'avez qu'une poignée de threads, ces bugs seront probablement évidents car votre programme cessera de fonctionner correctement. Si vous avez un pool de threads qui crée plus de threads selon vos besoins, les threads obsolètes/bloqués risquent de ne pas être remarqués et s'accumuleront indéfiniment, ce qui provoquera une fuite de mémoire. Les threads sont susceptibles d'utiliser d'autres données dans votre application. Par conséquent, tout ce à quoi ils font directement référence ne sera jamais collecté.

Comme exemple de jouet:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

Appelez System.gc() tout ce que vous voulez, mais l'objet transmis à leakMe ne mourra jamais.

(*édité*)

11
Boann

Je pense qu'un exemple valide pourrait utiliser des variables ThreadLocal dans un environnement où les threads sont mis en pool.

Par exemple, utiliser des variables ThreadLocal dans Servlets pour communiquer avec d'autres composants Web, faire en sorte que les threads soient créés par le conteneur et conserver ceux inactifs dans un pool. Les variables ThreadLocal, si elles ne sont pas correctement nettoyées, y resteront jusqu'à ce que le même composant Web les remplace éventuellement.

Bien sûr, une fois identifié, le problème peut être résolu facilement.

10
mschonaker

L'intervieweur a peut-être été à la recherche d'une solution de référence circulaire:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

C'est un problème classique du comptage de références. Vous expliqueriez alors poliment que les machines virtuelles utilisent un algorithme beaucoup plus sophistiqué qui n’a pas cette limitation.

-Nous Tarle

10
Wesley Tarle

Une autre façon de créer des fuites de mémoire potentiellement énormes consiste à conserver les références à Map.Entry<K,V> d'un TreeMap.

Il est difficile de comprendre pourquoi cela s'applique uniquement à TreeMaps, mais en regardant l'implémentation, la raison pourrait en être que: a TreeMap.Entry stocke les références de ses frères et soeurs, donc si un TreeMap est prêt doit être collecté, mais une autre classe contient une référence à l’un quelconque de ses Map.Entry, puis la carte entière sera conservée en mémoire.


Scénario réel:

Imaginez avoir une requête de base de données qui renvoie une grande structure de données TreeMap. Les utilisateurs utilisent généralement TreeMaps pour conserver l'ordre d'insertion des éléments.

public static Map<String, Integer> pseudoQueryDatabase();

Si la requête a été appelée de nombreuses fois et que, pour chaque requête (ainsi, pour chaque Map renvoyé), vous enregistrez un Entry, la mémoire continuerait de croître constamment.

Considérez la classe de wrapper suivante:

class EntryHolder {
    Map.Entry<String, Integer> entry;

    EntryHolder(Map.Entry<String, Integer> entry) {
        this.entry = entry;
    }
}

Application:

public class LeakTest {

    private final List<EntryHolder> holdersCache = new ArrayList<>();
    private static final int MAP_SIZE = 100_000;

    public void run() {
        // create 500 entries each holding a reference to an Entry of a TreeMap
        IntStream.range(0, 500).forEach(value -> {
            // create map
            final Map<String, Integer> map = pseudoQueryDatabase();

            final int index = new Random().nextInt(MAP_SIZE);

            // get random entry from map
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue().equals(index)) {
                    holdersCache.add(new EntryHolder(entry));
                    break;
                }
            }
            // to observe behavior in visualvm
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public static Map<String, Integer> pseudoQueryDatabase() {
        final Map<String, Integer> map = new TreeMap<>();
        IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
        return map;
    }

    public static void main(String[] args) throws Exception {
        new LeakTest().run();
    }
}

Après chaque appel pseudoQueryDatabase(), les instances map devraient être prêtes pour la collecte, mais cela ne se produira pas, car au moins une Entry est stockée ailleurs.

En fonction de vos paramètres jvm, l'application peut se bloquer très tôt en raison de OutOfMemoryError.

Vous pouvez voir sur ce graphique visualvm que la mémoire continue de croître.

Memory dump - TreeMap

La même chose ne se produit pas avec une structure de données hachée (HashMap).

Ceci est le graphique lorsque vous utilisez un HashMap.

Memory dump - HashMap

La solution? Sauvegardez directement la clé/valeur (comme vous le faites probablement déjà) plutôt que de sauvegarder le Map.Entry.


J'ai écrit un repère plus complet ici .

10
Marko Pacak

Un exemple que j'ai récemment corrigé est la création de nouveaux objets GC et Image, mais en oubliant d'appeler la méthode dispose ().

Extrait GC javadoc:

Le code d'application doit explicitement appeler la méthode GC.dispose () pour libérer les ressources du système d'exploitation gérées par chaque instance lorsque ces instances ne sont plus nécessaires. Ceci est particulièrement important sous Windows 95 et Windows 98, où le système d'exploitation dispose d'un nombre limité de contextes de périphérique disponibles.

Image javadoc:

Le code d'application doit explicitement appeler la méthode Image.dispose () pour libérer les ressources du système d'exploitation gérées par chaque instance lorsque ces instances ne sont plus requises.

9
Scott

Je souhaite donner un conseil sur la manière de surveiller les fuites de mémoire dans une application à l'aide des outils disponibles dans la JVM. Il ne montre pas comment générer la fuite de mémoire mais explique comment le détecter avec le minimum d’outils disponibles.

Vous devez d'abord surveiller Java la consommation de mémoire.

Le moyen le plus simple consiste à utiliser l'utilitaire jstat fourni avec la machine virtuelle Java.

jstat -gcutil <process_id> <timeout>

Il rapportera la consommation de mémoire pour chaque génération (Young, Eldery and Old) et les heures de collecte des déchets (Young et Full).

Dès que vous remarquez que la récupération de place complète est exécutée trop souvent et prend trop de temps, vous pouvez supposer que l'application perd de la mémoire.

Ensuite, vous devez créer une image mémoire en utilisant l’utilitaire jmap:

jmap -dump:live,format=b,file=heap.bin <process_id>

Ensuite, vous devez analyser le fichier heap.bin avec Memory Analyzer, Eclipse Memory Analyzer (MAT) par exemple.

MAT analysera la mémoire et vous fournira des informations suspectes sur les fuites de mémoire.

8
Pavel Molchanov

Théoriquement, vous ne pouvez pas. Java le modèle de mémoire l’empêche. Cependant, étant donné que Java doit être implémenté, vous pouvez utiliser certaines mises en garde. Cela dépend de ce que vous pouvez utiliser:

  • Si vous pouvez utiliser native, vous pouvez allouer de la mémoire à laquelle vous ne renoncez pas plus tard.

  • Si ce n'est pas disponible, il y a un sale petit secret à propos de Java que peu de gens savent. Vous pouvez demander un tableau d’accès direct qui n’est pas géré par le GC. Vous pouvez donc l'utiliser facilement pour créer une fuite de mémoire. Cela est fourni par DirectByteBuffer (http://download.Oracle.com/javase/1.5.0/docs/api/Java/nio/ByteBuffer.html#allocateDirect(int)).

  • Si vous ne pouvez utiliser aucun de ceux-ci, vous pouvez toujours créer une fuite de mémoire en trompant le CPG. La machine virtuelle Java est implémentée à l'aide d'un garbage collection Generational. Cela signifie que le tas est divisé en zones: jeunes, adultes et aînés. Un objet lors de sa création commence dans la zone jeune. Comme il est de plus en plus utilisé, il progresse d'adulte jusqu'à devenir aîné. Un objet qui atteint la zone de personnes âgées ne sera probablement pas récupéré. Vous ne pouvez pas être sûr qu'un objet fuit et si vous demandez un arrêt et que vous nettoyez le CPG, vous pourrez le nettoyer, mais il y aura une fuite pendant une longue période. Plus d'infos sur (http://Java.Sun.com/docs/hotspot/gc1.4.2/faq.html)

  • De plus, il n'est pas nécessaire que les objets de classe soient soumis à GC. Peut-être un moyen de le faire.

8
Artur Ventura

Un thread qui ne se termine pas (par exemple, dort indéfiniment dans sa méthode d'exécution). Ce ne sera pas la poubelle collectée même si nous perdons une référence à elle. Vous pouvez ajouter des champs pour que l’objet fil soit aussi gros que vous le souhaitez.

La meilleure réponse à l'heure actuelle énumère plus d'astuces à ce sujet, mais celles-ci semblent redondantes.

8
h22

Lance une exception non gérée de la méthode finalize.

7
Basanth Roy

La plupart des fuites de mémoire observées dans Java concernent des processus désynchronisés.

Le processus A parle à B via TCP et demande au processus B de créer quelque chose. B attribue à la ressource un identifiant, par exemple 432423, que A stocke dans un objet et utilise lorsqu’il parle à B. À un moment donné, l’objet dans A est récupéré par un garbage collection (peut-être à cause d’un bogue), mais A ne dit jamais à B que ( peut-être un autre bug).

Maintenant, A n'a plus l'ID de l'objet qu'il a créé dans la RAM de B, et B ne sait pas que A n'a plus de référence à l'objet. En effet, l'objet est une fuite.

7
arnt

Il existe de nombreuses réponses sur la manière de créer une fuite de mémoire en Java, mais veuillez noter le point posé lors de l'entretien.

"comment créer une fuite de mémoire avec Java?" est une question ouverte dont le but est d'évaluer le degré d'expérience d'un développeur.

Si je vous demande "Avez-vous de l'expérience dans le dépannage des fuites de mémoire en Java?", Votre réponse serait un simple "Oui". Il me faudrait ensuite poursuivre avec "Pourriez-vous me donner des exemples où vous souhaitez résoudre les problèmes de fuites de mémoire?", Auquel vous me donneriez un ou deux exemples.

Cependant, lorsque l'intervieweur demande "comment créer une fuite de mémoire avec Java?" la réponse attendue devrait suivre ces lignes:

  • J'ai rencontré une fuite de mémoire ... (dites quand) [ça me montre l'expérience]
  • Le code qui le causait était ... (expliquez le code) [vous l'avez corrigé vous-même]
  • Le correctif que j'ai appliqué était basé sur ... (expliquer le correctif) [cela me donne l'occasion de poser des questions précises sur le correctif]
  • Le test que j'ai fait était ... [me donne la chance de demander d'autres méthodologies de test]
  • Je l'ai documenté de cette façon ... [points supplémentaires. Bien si vous l'avez documenté]
  • Donc, il est raisonnable de penser que, si nous suivons cette procédure dans l’ordre inverse, c’est-à-dire: corrigez le code que j’ai corrigé et supprimez mon correctif, nous aurions une fuite de mémoire.

Lorsque le développeur ne parvient pas à suivre cette ligne de pensée, j'essaie de le guider lui demandant "Pourriez-vous me donner un exemple de la façon dont Java fuite de mémoire?", Suivi de "Avez-vous déjà eu à réparer fuite de mémoire en Java? "

Notez que je pas demande un exemple sur la fuite de mémoire en Java. Ce serait idiot. Qui serait intéressé par un développeur capable d'écrire efficacement du code qui perd de la mémoire?

7
Alexandre Santos

Si la taille maximale du tas est X. Y1 .... Yn Nombre d'instances Donc, mémoire totale = nombre d'instances X Octets par instance.Si X1 ...... Xn est un nombre d'octets par instances.Ensuite, mémoire totale (M) = Y1 * X1 + ..... + Yn * Xn. Donc, si M> X, il dépasse l’espace de tas. Ce qui suit peut être les problèmes dans le code 1.Utilisation de plus d'instances variable que locale. 2.Créer des instances à chaque fois au lieu de regrouper des objets. 3.Non créant l'objet à la demande. 4. Rendre la référence d'objet nulle après l'achèvement de l'opération. Encore une fois, recréer lorsqu'elle est demandée dans le programme.

7
abishkar bhattarai

Quelques suggestions:

  • utiliser commons-logging dans un conteneur de servlets (peut-être un peu provocant)
  • démarrer un thread dans un conteneur de servlet et ne pas revenir de sa méthode d'exécution
  • charge les gifs animés dans un conteneur de servlets (cela lancera un fil d'animation)

Les effets ci-dessus pourraient être "améliorés" en redéployant l'application;)

Récemment tombé sur ceci:

  • Appel de "new Java.util.Zip.Inflater ();" sans appeler "Inflater.end ()" jamais

Lire http://bugs.Sun.com/bugdatabase/view_bug.do?bug_id=5072161 et problèmes liés pour une discussion approfondie.

7
Peter

Une possibilité consiste à créer un wrapper pour un ArrayList qui fournit uniquement une méthode: celle qui ajoute des éléments à ArrayList. Rendre le ArrayList lui-même privé. A présent, construisez l'un de ces objets wrapper dans une étendue globale (en tant qu'objet statique dans une classe) et qualifiez-le avec le mot clé final (par exemple, public static final ArrayListWrapper wrapperClass = new ArrayListWrapper()). Alors maintenant, la référence ne peut pas être modifiée. C’est-à-dire que wrapperClass = null ne fonctionnera pas et ne pourra pas être utilisé pour libérer de la mémoire. Mais il n'y a également aucun moyen de faire quoi que ce soit avec wrapperClass autre que d'y ajouter des objets. Par conséquent, tous les objets que vous ajoutez à wrapperClass sont impossibles à recycler.

6
Gravity

Une fuite de mémoire dans Java n'est pas votre fuite de mémoire C/C++ typique.

Pour comprendre le fonctionnement de la machine virtuelle, lisez le Comprendre la gestion de la mémoire .

Fondamentalement, la partie importante est:

Le modèle Mark and Sweep

La machine virtuelle Java de JRockit utilise le modèle de récupération de place mark and sweep pour effectuer la collecte de place du segment de mémoire entier. Un ramassage des ordures par balayage et balayage consiste en deux phases, la phase de marquage et la phase de balayage.

Au cours de la phase de marquage , tous les objets accessibles depuis les threads Java, les descripteurs natifs et les autres sources racine sont marqués comme étant en vie , comme les objets qui sont accessibles à partir de ces objets et ainsi de suite. Ce processus identifie et marque tous les objets encore utilisés, le reste pouvant être considéré comme de la foutaise.

Pendant la phase de balayage, le tas est parcouru pour trouver les espaces entre les objets vivants. Ces lacunes sont enregistrées dans une liste libre et sont mises à disposition pour une nouvelle attribution d'objets.

La JVM JRockit utilise deux versions améliorées du modèle mark et sweep. L'un est principalement une marque concurrente et un balayage et l'autre est une marque parallèle et un balayage. Vous pouvez également combiner les deux stratégies, en exécutant, par exemple, principalement le marquage simultané et le balayage parallèle.

Donc, pour créer une fuite de mémoire en Java; le moyen le plus simple de le faire est de créer une connexion à une base de données, de travailler, et tout simplement de ne pas Close() it; puis générer une nouvelle connexion de base de données tout en restant dans la portée. Ce n'est pas difficile à faire en boucle par exemple. Si un utilisateur extrait d'une file d'attente et insère dans une base de données, vous pouvez facilement créer une fuite de mémoire en oubliant les connexions Close() ou en les ouvrant lorsque cela n'est pas nécessaire, etc.

Vous finirez par consommer le tas alloué à la JVM en oubliant de Close() la connexion. La corbeille JVM se ramassera comme une folle. résultant finalement en des erreurs Java.lang.OutOfMemoryError: Java heap space. Il convient de noter que l'erreur ne signifie pas forcément qu'il y a une fuite de mémoire. cela pourrait simplement signifier que vous n'avez pas assez de mémoire; Des bases de données telles que Cassandra et ElasticSearch, par exemple, peuvent générer cette erreur car elles ne disposent pas de suffisamment d'espace disque.

Cela vaut la peine de noter que cela est vrai pour toutes les langues du GC. Ci-dessous, quelques exemples que j'ai vu travailler en tant que SRE:

  • Noeud utilisant Redis en file d'attente; l'équipe de développement a créé de nouvelles connexions toutes les 12 heures et a oublié de fermer les anciennes. Finalement, le noeud était OOMd car il utilisait toute la mémoire.
  • Golang (je suis coupable de celui-ci); analyser de gros fichiers json avec json.Unmarshal puis transmettre les résultats par référence et les garder ouverts. Finalement, cela a eu pour résultat que tout le tas était consommé par des références accidentelles que je gardais ouvertes pour décoder JSON.
6
user1529891

La méthode String.substring dans Java 1.6 crée une fuite de mémoire. Cet article de blog l'explique.

http://javarevisited.blogspot.com/2011/10/how-substring-in-Java-works.html

5
Viraj

Dans Java, une "fuite de mémoire" signifie principalement que vous utilisez trop de mémoire, ce qui est différent de celui en C où vous n'utilisez plus la mémoire mais oubliez de la rendre (libre). Quand un intervieweur pose des questions sur Java fuites de mémoire, il pose des questions sur l'utilisation de la mémoire de la machine virtuelle qui semble continuer à augmenter, et ils ont déterminé que le redémarrage de la machine virtuelle est la meilleure solution. (sauf si l'intervieweur est extrêmement techniquement averti)

Répondez donc à cette question comme s’ils demandaient en quoi l’utilisation de la mémoire de la machine virtuelle Java augmentait avec le temps. De bonnes réponses consisteraient à stocker trop de données dans une session Http avec un délai d’attente trop long ou un cache en mémoire mal implémenté (Singleton) qui ne vide jamais les anciennes entrées. Une autre réponse potentielle est d'avoir beaucoup de JSP ou de classes générées dynamiquement. Les classes sont chargées dans une zone de mémoire appelée PermGen qui est généralement petite et la plupart des machines virtuelles n'implémentent pas le déchargement de classe.

5
Chase

Swing est très facile avec les dialogues. Créez un JDialog, montrez-le, l'utilisateur le ferme, fuyez! Vous devez appeler dispose() ou configurer setDefaultCloseOperation(DISPOSE_ON_CLOSE)

5
jorgeu

Lapsed Listerners est un bon exemple de fuite de mémoire: Object est ajouté en tant que Listener. Toutes les références à l'objet sont annulées lorsque l'objet n'est plus nécessaire. Cependant, le fait de ne pas supprimer l'objet de la liste des écouteurs le maintient en vie et répond même aux événements, gaspillant ainsi de la mémoire et du processeur. Voir http://www.drdobbs.com/jvm/Java-qa/184404011

4
Tarik

Si vous n'utilisez pas un ramasse-miettes compactant, vous pouvez avoir une sorte de fuite de mémoire en raison de la fragmentation du tas.

4
user1050755

tiliser négligemment une classe interne non statique dans une classe qui a son propre cycle de vie.

En Java, classes internes et anonymes non statiques possède une référence implicite à sa classe externe. Les classes internes statiques, en revanche, ne pas.

Voici un exemple courant de fuite de mémoire dans Android, ce qui n’est pas évident:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() { //non-static inner class, holds the reference to the SampleActivity outter class
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for a long time.
    mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
      @Override
      public void run() {
     //.... 
      }
    }, SOME_TOME_TIME);

    // Go back to the previous Activity.
    finish();
  }}

Cela évitera que le contexte d'activité ne soit collecté.

4
Jaskey

Exemple en temps réel de fuite de mémoire avant JDK 1.7

supposons que vous lisiez un fichier de 1000 lignes de texte et le gardiez dans l'objet String

String fileText = 1000 characters from file

fileText = fileText.subString(900, fileText.length());

Dans le code ci-dessus, j'ai d'abord lu 1000 caractères, puis une sous-chaîne pour obtenir seulement 100 derniers caractères. Maintenant, fileText ne devrait faire référence qu'à 100 caractères et tous les autres caractères devraient être récupérés car j'ai perdu la référence, mais avant, la fonction de sous-chaîne JDK 1.7 faisait indirectement référence à la chaîne d'origine des 100 derniers caractères et empêchait la chaîne entière d'être récupérée. en mémoire jusqu'à ce que vous perdiez la référence de la sous-chaîne.

vous pouvez créer un exemple de fuite de mémoire comme ci-dessus

0
Praveen Kumar

une fuite de mémoire est un type de fuite de ressources qui se produit lorsqu'un programme informatique gère de manière incorrecte les allocations de mémoire de sorte que la mémoire qui n'est plus n'est plus libérée => définition du wiki

C'est un peu un sujet relativement contextuel, vous pouvez simplement en créer un en fonction de vos goûts tant que les références inutilisées ne seront jamais utilisées par les clients mais resteront en vie.

Le premier exemple doit être une pile personnalisée sans la suppression de les références obsolètes dans effective Java élément 6 .

Bien sûr, il y en a beaucoup plus longtemps aussi longtemps que vous le souhaitez, mais si nous examinons simplement les classes intégrées Java, il pourrait s'agir de

subList()

Vérifions quelques codes super silly pour produire la fuite.

public class MemoryLeak {
    private static final int HUGE_SIZE = 10_000;

    public static void main(String... args) {
        letsLeakNow();
    }

    private static void letsLeakNow() {
        Map<Integer, Object> leakMap = new HashMap<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            leakMap.put(i * 2, getListWithRandomNumber());
        }
    }



    private static List<Integer> getListWithRandomNumber() {
        List<Integer> originalHugeIntList = new ArrayList<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            originalHugeIntList.add(new Random().nextInt());
        }
        return originalHugeIntList.subList(0, 1);
    }
}

En fait, il existe un autre moyen de provoquer une fuite de mémoire en utilisant HashMap en tirant parti de son processus de recherche. Il y a en fait deux types:

  • hashCode() est toujours le même mais equals() est différent;
  • use random hashCode() et equals() toujours vrai;

Pourquoi?

hashCode() -> bucket => equals() pour localiser la paire


J'étais sur le point de mentionner substring() d'abord, puis subList(), mais il semble que ce problème soit déjà résolu car sa source est présente dans JDK 8.

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
0
Hearen