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 de mémoire avec Java. Inutile de dire que je me sentais assez stupide n'ayant aucune idée de la façon de commencer à en créer un.

Quel serait un exemple?

Author: Mat Banik, 2011-06-24

30 answers

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 utilise un pool de threads pour fuir encore plus rapidement).
  2. Le thread charge une classe via un ClassLoader (éventuellement personnalisé).
  3. La classe alloue un gros morceau de mémoire (par exemple new byte[1000000]), stocke une référence forte dans un champ statique, puis stocke une référence à elle-même dans un ThreadLocal. L'allocation de la mémoire supplémentaire est facultative (une fuite de l'instance de classe suffit), mais cela rendra la fuite beaucoup plus rapide.
  4. Le thread efface toutes les références à la classe personnalisée ou au ClassLoader à partir duquel il a été chargé.
  5. Répéter.

Cela fonctionne parce que le ThreadLocal conserve une référence à l'objet, 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 charger.

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

Une variation de ce modèle explique pourquoi les conteneurs d'applications (comme Tomcat) peuvent fuir la mémoire comme un tamis si vous redéployez fréquemment des applications qui utilisent ThreadLocals en aucune façon. (Étant donné que le conteneur d'application utilise des Threads comme décrit, et chaque fois que vous redéployer l'application, un nouveau ClassLoader est utilisé.)

Update : Puisque beaucoup de gens continuent à le demander, voici un exemple de code qui montre ce comportement en action.

 1994
Author: Daniel Pryden, 2017-09-06 02:56:18

Champ statique contenant la référence de l'objet [champ final esp]

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

L'Appel String.intern() sur 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();

(Unclosed) 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 à partir du garbage collector de la JVM, telles que la mémoire allouée via des méthodes natives

Dans les applications Web, certains objets sont stockés dans la portée de l'application jusqu'à l'application est explicitement arrêtée ou supprimée.

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

Options JVM incorrectes ou inappropriées , telles que l'option noclassgc sur IBM JDK qui empêche la récupération de place de classe inutilisée

Voir IBM jdk paramètres.

 1087
Author: Prashant Bhate, 2018-01-30 23:32:27

Une chose simple à faire est d'utiliser un HashSet avec une mauvaise (ou inexistante) hashCode() ou equals(), puis continuer à ajouter des "doublons". Au lieu d'ignorer les doublons comme il se doit, l'ensemble ne fera que croître 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.
 405
Author: Peter Lawrey, 2011-06-24 21:50:56

Ci-dessous, il y aura un cas non évident où Java fuit, en plus du cas standard des auditeurs oubliés, des références statiques, des clés fausses/modifiables dans les hashmaps, ou simplement des threads bloqués sans aucune chance de mettre fin à leur cycle de vie.

  • File.deleteOnExit() - fuit toujours la chaîne, si la chaîne est une sous-chaîne, la fuite est encore pire (le char sous-jacent [] est également divulgué) - dans la sous-chaîne Java 7 copie également le char[], donc le dernier ne s'applique pas ; @ Daniel, pas besoin pour les votes, si.

Je vais me concentrer sur les threads pour montrer le danger des threads non gérés principalement, je ne souhaite même pas toucher swing.

  • Runtime.addShutdownHook et pas la retirer... et puis 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é, fuir efficacement le ThreadGroup. JGroup a la fuite dans GossipRouter.

  • Créer, mais pas démarrer, un Thread entre dans la même catégorie que surtout.

  • La création d'un thread hérite des ContextClassLoader et AccessControlContext, plus les ThreadGroup et any InheritedThreadLocal, toutes ces références sont des fuites potentielles, ainsi que les classes entières chargées par le classloader et toutes les références statiques, et ja-ja. L'effet est particulièrement visible avec l'ensemble du framework j. u. c. Executor qui dispose d'une interface super simple ThreadFactory, mais la plupart des développeurs n'ont aucune idée du danger qui se cache. De plus, de nombreuses bibliothèques démarrent des threads sur demande (beaucoup trop d'industrie les bibliothèques populaires).

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

  • Appeler ThreadGroup.destroy() lorsque le ThreadGroup n'a pas de threads lui-même, mais qu'il conserve toujours les ThreadGroups enfants. Une mauvaise fuite qui empêchera le ThreadGroup à supprimer de son parent, mais tous les enfants deviennent un-enumerateable.

  • L'utilisation de WeakHashMap et de la valeur (in)fait directement référence à la clé. C'est difficile à trouver sans décharge de tas. Cela s'applique à tous les Weak/SoftReference étendus qui pourraient garder une référence dure à l'objet gardé.

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

  • En utilisant InflaterInputStream en passant new java.util.zip.Inflater() dans le constructeur (PNGImageDecoder par exemple) et ne pas appeler end() du gonfleur. Eh bien, si vous passez dans le constructeur avec juste new, aucune chance... Et oui, appeler close() sur le flux ne ferme pas le gonfleur s'il est passé manuellement en tant que paramètre constructeur. Ce n'est pas une vraie fuite car elle serait publiée par le finaliseur... lorsqu'il le juge nécessaire. Jusqu'à ce moment, il mange tellement la mémoire native qu'il peut amener Linux oom_killer à tuer le processus en toute impunité. Le problème principal est que la finalisation en Java est très peu fiable et G1 a empiré jusqu'à 7.0.2. Morale de l'histoire: libérez les ressources natives dès que vous le pouvez; le finaliseur est tout simplement trop pauvre.

  • Le même cas avec java.util.zip.Deflater. Celui-ci est bien pire car Deflater a faim de mémoire en Java, c'est-à-dire utilise toujours 15 bits (max) et 8 niveaux de mémoire (9 est max) allouant plusieurs centaines de Ko de mémoire native. Heureusement, Deflater n'est pas largement utilisé et à ma connaissance, JDK ne contient aucun abus. Toujours faire appel à 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 sur demande.)

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

 237
Author: bestsss, 2016-02-16 15:52:26

La plupart des exemples ici sont "trop complexes". Ils sont des cas limites. Avec ces exemples, le programmeur a fait une erreur (comme ne pas redéfinir equals/hashcode), ou a été mordu par un cas d'angle de la JVM/JAVA (charge de classe avec statique...). 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 garbage collector ne libère que ce qui n'est plus référencé. Nous en tant que développeurs Java ne nous soucions pas mémoire. Nous l'allouons en cas de besoin et le laissons être libéré automatiquement. Fin.

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

Bien sûr, toutes sortes d'auditeurs( comme les auditeurs de l'interface utilisateur), les caches ou tout état partagé de longue durée ont tendance à produire une fuite de mémoire si elle n'est pas correctement gérée. Ce qui doit être compris, c'est que ce n'est pas un cas d'angle Java, ou un problème avec le garbage collector. C'est un problème de conception. Nous concevons que nous ajoutons un écouteur à un objet de longue durée, mais nous ne supprimons pas l'écouteur 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 graphe complexe qui stocke l'état précédent nécessaire à un calcul. Mais l'état précédent est lui-même lié à l'état avant et ainsi de suite.

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

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 indicateur qu'il manque quelque chose, soit même des problèmes de conception.

 165
Author: Nicolas Bousquet, 2018-02-13 07:27:47

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, il est, et il y a beaucoup d'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 la théorie et la réalité?
  • le candidat comprendre comment fonctionne la collecte des ordures?
  • Ou comment la collecte des ordures est censée fonctionner dans un cas idéal?
  • Savent-ils qu'ils peuvent appeler d'autres langues via des interfaces natives?
  • Savent-ils fuir 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 derrière la scène en Java?

Je lis votre méta-question comme "Quelle est une réponse que j'aurais pu utiliser dans cette situation d'interview". Et par conséquent, je vais me concentrer sur les compétences d'entrevue au lieu de Java. Je crois que vous êtes plus susceptible de répéter la situation de ne pas connaître la réponse à une question dans une interview que de devoir savoir comment faire fuir Java. Donc, j'espère que cela va aider.

L'une des compétences les plus importantes que vous pouvez développer pour l'entrevue est d'apprendre à écouter activement les questions et de travailler avec l'intervieweur pour extraire leur intention. Non seulement cela vous permet de répondre à leur questionnez la façon dont ils veulent, mais montre également que vous avez des compétences de communication vitales. Et quand il s'agit de choisir entre de nombreux développeurs tout aussi talentueux, j'engagerai celui qui écoute, pense et comprend avant de répondre à chaque fois.

 138
Author: PlayTank, 2015-01-27 12:52:33

Ce qui suit est un exemple assez inutile, si vous ne comprenez pas JDBC. Ou du moins comment JDBC s'attend à ce qu'un développeur ferme Connection, Statement et ResultSet instances avant de les rejeter ou de perdre leurs références, au lieu de s'appuyer sur l'implémentation 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 restera ouverte, jusqu'à ce que le garbage collector arrive et voit qu'il est inaccessible. GC invoquera la méthode finalize, mais il existe des pilotes JDBC qui n'implémentent pas le finalize, du moins pas de la même manière que Connection.close est implémenté. Le comportement résultant est que si la mémoire est récupérée en raison de la collecte d'objets inaccessibles, les ressources (y compris la mémoire) associées à l'objet Connection peuvent tout simplement ne pas être récupérées.

Dans un tel cas où la méthode Connection's finalize ne nettoie pas tout, on pourrait en fait trouver que la connexion physique au le serveur de base de données durera plusieurs cycles de récupération de place, jusqu'à ce que le serveur de base de données finisse par comprendre 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 levées lors de la finalisation. Le comportement résultant est que toute mémoire associée à l'objet maintenant "dormant" ne sera pas récupérée, car finalize est garanti pour être invoqué une seule fois.

Le scénario ci-dessus de rencontrer des exceptions lors de la finalisation de l'objet est lié à un autre scénario qui pourrait éventuellement conduire à une fuite de mémoire - résurrection de l'objet. La résurrection d'objet se fait souvent intentionnellement en créant une référence forte à l'objet en cours de finalisation, à partir d'un autre objet. Lorsque la résurrection d'objet est mal utilisée, cela entraînera une fuite de mémoire en combinaison avec 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 en supprimer (bien que vous devriez vous débarrasser des éléments dont vous n'avez plus besoin), ou
  • Ouvrant Sockets ou Files, mais ne les fermant pas lorsqu'ils ne sont plus nécessaires (similaire à l'exemple ci-dessus impliquant la classe Connection).
  • Ne pas décharger les Singletons lors de la descente d'une application Java EE. Apparemment, le Classloader qui a chargé la classe singleton conservera un la référence à la classe, et donc l'instance singleton ne sera jamais collectée. Lorsqu'une nouvelle instance de l'application est déployée, une nouvelle classe de chargement est généralement créé, et l'ancien chargeur de classe continuera d'exister grâce à l'singleton.
 117
Author: Vineet Reynolds, 2016-02-16 15:38:36

Probablement l'un des exemples les plus simples d'une fuite de mémoire potentielle, et comment l'éviter, est l'implémentation de ArrayList.supprimer(int):

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 énorme objet en vie ...

 106
Author: meriton, 2015-09-01 04:50:05

Chaque fois que vous conservez des références à des objets dont vous n'avez plus besoin, vous avez une fuite de mémoire. Voir Gestion des fuites de mémoire dans les programmes Java pour des exemples de la façon dont les fuites de mémoire se manifestent en Java et ce que vous pouvez faire à ce sujet.

 64
Author: Bill the Lizard, 2011-06-24 16:18:07

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

Le code ne compile pas dans Eclipse ID-compilez-le en utilisant la commande javac (pendant la compilation, vous obtiendrez des avertissements)

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();
        }
    }

}
 46
Author: stemm, 2016-01-05 08:37:35

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

"Une fuite de mémoire, en informatique (ou fuite, dans ce contexte), se produit lorsqu'un programme informatique consomme de la mémoire mais est incapable de la restituer au système d'exploitation."(Wikipedia)

La réponse facile est: Vous ne pouvez pas. Java gère automatiquement la mémoire et libère des ressources qui ne sont pas nécessaires pour vous. Vous ne pouvez pas arrêter ce qui se passe. Il sera TOUJOURS en mesure de libérer le ressources. Dans les programmes avec gestion manuelle de la mémoire, c'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 que malloc a renvoyé et appelez free() dessus. Mais si vous n'avez plus le pointeur (écrasé, ou la durée de vie dépassée), vous êtes malheureusement incapable de libérer cette mémoire et vous avez donc une fuite de mémoire.

Toutes les autres réponses jusqu'à présent ne sont pas vraiment dans ma définition des fuites de mémoire. Ils visent tous à remplir la mémoire avec des trucs inutiles très vite. Mais à tout moment, vous pouvez toujours déréférencer les objets que vous avez créés et libérer ainsi la mémoire - > PAS de FUITE. la réponse d'acconrad se rapproche cependant assez comme je dois l'admettre puisque sa solution consiste effectivement à simplement "planter" le garbage collector en le forçant dans une boucle sans fin).

La réponse longue est: Vous pouvez obtenir une fuite de mémoire en écrivant une bibliothèque pour Java à l'aide du JNI, qui peut avoir une gestion manuelle de la mémoire et donc avoir des fuites de mémoire. Si vous appelez cette bibliothèque, votre processus java va fuir la mémoire. Ou, vous pouvez avoir des bogues dans la JVM, de sorte que la JVM perd de la mémoire. Il y a probablement des bugs dans la JVM, il peut même y en avoir des connus car la récupération de place n'est pas si triviale, mais c'est toujours un bug. Par sa conception ce n'est pas possible. Vous demandez peut-être du code java qui est effectué par un tel bogue. Désolé, je n'en connais pas et ce pourrait bien ne plus être un bug dans la prochaine version de Java de toute façon.

 40
Author: yankee, 2017-05-23 11:47:29

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);
    }
}

Parce 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 toute la chaîne d'origine en mémoire, même si vous pensez peut-être que vous tenez juste à une chaîne à un seul caractère.

La façon d'éviter de stocker un indésirable référence à la chaîne d'origine est de faire quelque chose comme ceci:

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

Pour plus de méchanceté, vous pouvez également .intern() la sous-chaîne:

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

Cela gardera à la fois la chaîne longue d'origine et la sous-chaîne dérivée en mémoire même après que l'instance StringLeaker a été supprimée.

 33
Author: Jon Chambers, 2011-07-21 19:00:35

Un exemple courant de ceci dans le code GUI est lors de la création d'un widget/composant et de l'ajout d'un écouteur à un objet statique/application, puis de ne pas supprimer l'écouteur lorsque le widget est détruit. Non seulement vous obtenez une fuite de mémoire, mais aussi un coup de performance comme quand tout ce que vous écoutez des événements de feux, tous vos anciens auditeurs sont appelés aussi.

 33
Author: pauli, 2016-02-16 15:40:24

Prenez n'importe quelle application Web s'exécutant dans n'importe quel conteneur de servlet (Tomcat, Jetty, Glassfish, peu importe...). Redéploy l'application 10 ou 20 fois de suite (il peut suffire de simplement toucher la GUERRE dans le répertoire autodeploy du serveur.

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

Le problème est que la durée de vie du conteneur est plus longue que la durée de vie de votre application. Vous devez vous assurer que toutes les références que le conteneur peut avoir à des objets ou des classes de votre application peuvent être collectées.

S'il n'y a qu'une seule référence qui survit au non-déploiement de votre application Web, le classloader correspondant et par conséquent toutes les classes de votre application Web ne peuvent pas être collectées.

Threads démarrés par votre application, ThreadLocal les variables, les appenders de journalisation sont quelques-uns des suspects habituels pour provoquer des fuites de classloader.

 32
Author: Harald Wellmann, 2011-07-03 22:41:17

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.

 29
Author: Rogach, 2011-06-24 21:48:48

J'ai eu une belle "fuite de mémoire" par rapport à l'analyse PermGen et XML une fois. L'analyseur XML que nous avons utilisé (je ne me souviens pas lequel c'était) a fait une chaîne.stagiaire() sur les noms de balises, pour rendre la comparaison plus rapide. Un de nos clients a eu la bonne idée de stocker des valeurs de données non pas dans des attributs XML ou du texte, mais sous forme de tagnames, nous avons donc eu un document comme:

<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), qui étaient uniques et arrivaient à un taux de 10 à 15 millions jour. Cela fait 200 Mo de déchets par jour, ce qui n'est plus jamais nécessaire, et jamais GCed (puisqu'il est dans PermGen). Nous avions permgen réglé sur 512 Mo, il a donc fallu environ deux jours pour que l'exception de mémoire insuffisante (OOME) arrive...

 28
Author: Ron, 2016-02-16 15:44:24

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

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

Afin de stocker des balises spécifiques aux threads, la classe NDC de log4j utilise une table de hachage qui est saisie par l'objet Thread lui-même (as contrairement à dire l'id de thread), et donc jusqu'à ce que la balise NDC reste en mémoire tous les objets qui se bloquent de l'objet thread restent également en mémoire. Dans notre application Web, nous utilisons NDC pour marquer logoutputs avec un id de demande pour 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 s'est produit lorsque, au cours du traitement d'une demande, un thread enfant a été généré, 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();
    }
}    

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

 22
Author: Puneet, 2014-03-07 21:29:39

Je pensais qu'il était intéressant que personne n'utilise les exemples de classe interne. Si vous avez une classe interne; il maintient intrinsèquement une référence à la classe conteneur. Bien sûr, ce n'est pas techniquement une fuite de mémoire car Java finira par le nettoyer; mais cela peut entraîner des classes à traîner plus longtemps que prévu.

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

Maintenant, si vous appelez Example1 et obtenez un Example2 en rejetant Example1, vous aurez intrinsèquement toujours un lien vers un objet Example1.

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 a également entendu une rumeur selon laquelle si vous avez une variable qui existe plus longtemps qu'un laps de temps spécifique; Java suppose qu'elle existera toujours et n'essaiera en fait jamais de la nettoyer si elle ne peut plus être atteinte dans le code. Mais c'est totalement infondées.

 21
Author: Suroot, 2011-07-09 01:23:55

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.
  • Cela empire avec le temps.
  • Le garbage collector ne peut pas le nettoyer.

exemple Typique:

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

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 dans la 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.
  • ajouter plus de arbres de référence complexes .
  • ou des fuites causées par bibliothèques tierces .

Ce qui arrive souvent:

Si cet objet Info a des références à d'autres objets, qui ont encore des références à d'autres objets. D'une certaine manière vous pourriez également considérer cela comme une sorte de fuite de mémoire, (causée par une mauvaise conception).

 20
Author: bvdb, 2014-10-28 13:18:50

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); }
}
 17
Author: duffymo, 2011-07-21 17:32:58

Vous pouvez créer une fuite de mémoire mobile 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 fuit 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
Author: sethobrien, 2011-07-22 08:05:07

L'intervieweur cherchait probablement une référence circulaire comme le code ci-dessous (qui incidemment ne fuit que la mémoire dans les très anciennes JVM qui utilisaient le comptage des références, ce qui n'est plus le cas). Mais c'est une question assez vague, c'est donc une excellente occasion de 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 des références, le code ci-dessus aurait une fuite de mémoire. Mais la plupart des JVM modernes n'utilisent plus le comptage de références, la plupart utilisez un ramasse-miettes de balayage, qui collectera en fait cette mémoire.

Ensuite, vous pouvez expliquer la création d'un objet qui a 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. */
    }
}

Ensuite, vous pouvez expliquer qu'il s'agit techniquement d'une fuite de mémoire, mais en réalité, la fuite est causée par du code natif dans la JVM allouant des ressources natives sous-jacentes, qui n'ont pas été libérées par votre code Java.

À la fin de la journée, avec une JVM moderne, vous devez écrire du code Java qui alloue un natif ressource en dehors de la portée normale de la conscience de la JVM.

 16
Author: deltamind106, 2017-10-13 19:09:24

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

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

Rappelez-vous, les allocations de mémoire dans le code natif proviennent du tas JVM.

 15
Author: Paul Morie, 2011-07-21 20:52:40

Je suis tombé récemment sur une fuite de ressources plus subtile. Nous ouvrons des ressources via getResourceAsStream du chargeur de classe et il est arrivé que les poignées de flux d'entrée ne soient pas fermées.

Euh, vous pourriez dire, quel idiot.

Eh bien, ce qui rend cela intéressant est: de cette façon, vous pouvez fuir la mémoire de tas du processus sous-jacent, plutôt que du tas de JVM.

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

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, compiler et exécuter à 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 essentiellement rien, mais crée des objets InputStream non référencés. Ces objets seront ordures collectées immédiatement et donc, ne contribuent pas à la taille du tas. Il est important pour notre exemple de charger une ressource existante à partir d'un fichier jar, et la taille importe 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 OOM ici, car aucune référence n'est conservée, l'application continuera à fonctionner quelle que soit la taille des ITÉRATIONS choisies dans l'exemple ci-dessus. La consommation de mémoire de votre process (visible dans top (RES / RSS) ou process explorer) se développe à moins que l'application n'atteigne la commande wait. Dans la configuration ci-dessus, il allouera environ 150 Mo de mémoire.

Si vous voulez que l'application joue en toute sécurité, fermez le flux d'entrée à l'endroit où il a été 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
Author: Jay, 2012-09-12 20:07:37

Je ne pense pas que quelqu'un l'ait encore dit: vous pouvez ressusciter un objet en remplaçant la méthode finalize() de telle sorte que finalize() stocke une référence de ceci quelque part. Le garbage collector ne sera appelée qu'une fois sur l'objet, donc après que l'objet ne sera jamais détruit.

 14
Author: Ben, 2011-07-03 17:36:58

Comme beaucoup de gens l'ont suggéré, les fuites de ressources sont assez faciles à provoquer - comme les exemples JDBC. Les fuites de mémoire réelles sont un peu plus difficiles - surtout si vous ne comptez pas sur des bits cassés de la JVM pour le faire pour vous...

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 vraies fuites de mémoire. Si rien ne peut y accéder, il sera collecté, et si quelque chose peut y accéder, ce n'est pas un fuite...

Une façon dont a utilisé pour fonctionner - et je ne sais pas si c'est toujours le cas - est d'avoir une chaîne circulaire à trois profondeurs. Comme dans l'objet A a une référence à l'objet B, l'Objet B a une référence à l'Objet C et l'Objet C a une référence à l'objet A. Le GC était assez intelligent pour savoir qu'une chaîne à deux profondeurs - comme dans A > B - peut être collectée en toute sécurité si A et B ne sont accessibles..

 14
Author: Graham, 2011-07-21 17:55:51

Il y a beaucoup de situations différentes mémoire va fuir. Celui que j'ai rencontré, qui expose une carte qui ne devrait pas être exposée et utilisée ailleurs.

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
Author: Ben Xu, 2011-07-10 07:46:03

Les threads ne sont pas collectés tant qu'ils ne se terminent pas. Ils servent de racines de la collecte des ordures. Ils sont l'un des rares objets qui ne seront pas récupérés simplement en les oubliant ou en effaçant les références à eux.

Considérez: le modèle de base pour terminer un thread de travail est de 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 la variable peut ne pas être vue par le thread, il ne saura donc pas se terminer. Ou imaginez si certains threads veulent mettre à jour un objet partagé, mais bloquent tout en essayant de le verrouiller.

Si vous n'avez qu'une poignée de threads, ces bogues seront probablement évidents car votre programme cessera de fonctionner correctement. Si vous avez un pool de threads qui crée plus de threads au besoin, les threads obsolètes/bloqués peuvent ne pas être remarqués et s'accumuler indéfiniment, provoquant une fuite de mémoire. Les Threads sont susceptible d'utiliser d'autres données dans votre application, donc empêchera également tout ce qu'ils référencent directement d'être 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 passé à leakMe ne mourra jamais.

(*édité*)

 11
Author: Boann, 2017-05-23 12:02:58

Je pense qu'un exemple valide pourrait être l'utilisation de variables ThreadLocal dans un environnement où les threads sont regroupés.

Par exemple, utiliser des variables ThreadLocal dans des Servlets pour communiquer avec d'autres composants Web, avoir les threads créés par le conteneur et maintenir les inactifs dans un pool. Les variables ThreadLocal, si elles ne sont pas correctement nettoyées, y vivront jusqu'à ce que, éventuellement, le même composant web écrase leurs valeurs.

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

 9
Author: mschonaker, 2011-07-03 06:49:51

L'intervieweur pourrait avoir cherché 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;
        }
    }

Il s'agit d'un problème classique avec le comptage de référence des garbage collectors. Vous expliqueriez alors poliment que les JVM utilisent un algorithme beaucoup plus sophistiqué qui n'a pas cette limitation.

- Wes Tarle

 9
Author: Wesley Tarle, 2011-07-04 15:06:58