Créer 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 suis senti assez stupide n'ayant aucune idée de comment commencer à en créer un.

quel serait un exemple?

2723
demandé sur Mat Banik 2011-06-24 20:11:52

30 réponses

Voici une bonne façon de créer une véritable fuite de mémoire (objets inaccessibles par le code en cours d'exécution mais encore stockés en mémoire) en Java pur:

  1. l'application crée un fil long (ou utiliser un pool de fil pour fuir encore plus vite).
  2. le thread charge une classe via un ClassLoader (éventuellement personnalisé).
  3. la classe attribue un grand morceau de mémoire (par exemple new byte[1000000] ), stocke une forte référence à elle dans un statique et stocke ensuite une référence à lui - même dans un ThreadLocal. Allouer la mémoire supplémentaire est optionnel (la fuite de L'instance de classe est suffisante), mais cela rendra la fuite de travail que beaucoup plus rapide.
  4. le thread efface toutes les références à la classe custom ou au ClassLoader d'où il a été chargé.
  5. je répète.

cela fonctionne parce que le ThreadLocal garde une référence à l'objet, ce qui conserve une référence à sa classe, ce 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 JVM, surtout avant Java 7, parce que Classes et ClassLoaders étaient alloués directement dans permgen et n'étaient jamais GC'D du tout. Cependant, quelle que soit la manière dont la JVM gère le déchargement de classe, un ThreadLocal empêchera tout de même qu'un objet de classe soit récupéré.)

le motif est pourquoi les conteneurs d'application (comme Tomcat) peuvent perdre de la mémoire comme un tamis si vous redéployez fréquemment des applications qui se produisent à utiliser ThreadLocals de n'importe quelle façon. (Puisque le conteneur d'application utilise des Threads comme décrit, et chaque fois que vous redéployez l'application un nouveau ClassLoader est utilisé.)

Update : comme beaucoup de gens le demandent, voici un exemple de code qui montre ce comportement en action .

1994
répondu Daniel Pryden 2017-09-06 02:56:18

champ Statique de portefeuille de référence de l'objet [esp champ final]

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

Appel String.intern() à 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 fermée) de cours d'eau ( 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 collecteur d'ordures de JVM, telles que la mémoire attribuée par des méthodes autochtones

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 IBM JDK qui empêche la collecte des ordures de classe inutilisée

Voir IBM jdk paramètres .

1087
répondu Prashant Bhate 2018-01-30 23:32:27

une chose simple à faire est d'utiliser un HashSet avec un incorrect (ou inexistant) hashCode() ou equals() , et puis de 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 autour 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
répondu Peter Lawrey 2011-06-24 21:50:56

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

  • File.deleteOnExit() - toujours fuit la chaîne, si la chaîne est un substrat, la fuite est encore pire (le char sous-jacent [] est également fuité) - en Java 7 substring copie également le char[] , donc le dernier ne s'applique pas ; @Daniel, pas besoin de votes, cependant.

je vais me concentrer sur les fils pour montrer le danger des fils non gérés la plupart du temps, ne veulent même pas toucher swing.

  • Runtime.addShutdownHook et ne pas enlever... et puis même avec removeShutdownHook en raison d'un bug dans la classe ThreadGroup concernant les threads Non démarrés, il peut ne pas être collecté, fuit effectivement le ThreadGroup. JGroup il y a une fuite dans GossipRouter.

  • créer, mais ne pas démarrer, un Thread entre dans la même catégorie que ci-dessus.

  • la création d'un thread hérite des ContextClassLoader et AccessControlContext , plus les ThreadGroup et n'importe quel InheritedThreadLocal , toutes ces références sont des fuites potentielles, avec toutes les classes chargées par le classloader et toutes les références statiques, et ja-ja. L'effet est particulièrement visible avec tout le J. U. C. Executor framework qui dispose d'une interface ThreadFactory très simple, mais la plupart des développeurs n'ont aucune idée du danger qui les guette. Aussi beaucoup de bibliothèques en faire des filets sur demande (beaucoup trop industrie bibliothèques populaires).

  • ThreadLocal caches; ceux qui sont mal dans de nombreux cas. Je suis sûr que tout le monde a vu un peu de caches simples basés sur ThreadLocal, bien les mauvaises nouvelles: si le fil continue plus que attendu la vie le ClassLoader contexte, il est un pur petit fuit gentil. Ne pas utiliser de caches ThreadLocal à moins que cela ne soit vraiment nécessaire.

  • appelant ThreadGroup.destroy() quand le ThreadGroup n'a pas de threads lui-même, mais il conserve encore les threadgroups enfants. Une mauvaise fuite qui empêchera le ThreadGroup de se retirer de son parent, mais tous les enfants deviennent impossibles à énumérer.

  • utilisant Weakhmap et la valeur (en)fait directement référence à la clé. C'est difficile à trouver sans une décharge en tas. Cela s'applique à tout Weak/SoftReference étendu qui pourrait garder une référence dure de retour à l'objet gardé.

  • en utilisant java.net.URL avec le protocole HTTP(S) et en chargeant la ressource à partir de(!). Celui-ci est spécial, le KeepAliveCache crée un nouveau thread dans le groupe de threads système qui fuit le classloader de contexte du thread courant. Le fil est créé sur le première requête quand il n'y a pas de fil vivant, donc soit vous avez de la chance, soit vous fuyez. la fuite est déjà corrigée en 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 fixe ) de créer des fils similaires.

  • utilisant InflaterInputStream 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 comme paramètre constructeur. Ce n'est pas une vraie fuite puisqu'elle serait libérée par le finaliseur... lorsqu'il le juge nécessaire. Jusqu'à ce moment, il mange la mémoire native si mal qu'il peut causer Linux oom_killer de tuer le processus en toute impunité. Le principal problème est-ce que la finalisation en Java est très peu fiable et G1 a empiré jusqu'à 7.0.2. Morale de l'histoire: la libération ressources autochtones dès que vous le pouvez; le finaliseur est tout simplement trop pauvres.

  • le même cas avec java.util.zip.Deflater . Celui-ci est bien pire car Deflater est avide de mémoire en Java, c'est-à-dire qu'il utilise toujours 15 bits (max) et 8 niveaux de mémoire (9 est max) allouant plusieurs centaines de KB de mémoire native. Heureusement, Deflater n'est pas largement utilisé et à ma connaissance JDK ne contient pas d'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 d'autres gaspilleurs de temps que j'ai rencontrés sur demande.)

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

237
répondu bestsss 2016-02-16 15:52:26

la Plupart des exemples ici sont "trop complexe". 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 de coin du jvm/JAVA (charge de classe avec statique...). Je pense que ce n'est pas le genre d'exemple qu'un interviewer veut ou même le cas le plus commun.

mais il y a des cas vraiment plus simples pour des fuites de mémoire. Le éboueur ne libère que ce qui n'est plus référencé. Nous, en tant que Java les développeurs ne se soucient pas de la mémoire. Nous le répartissons en fonction des besoins et le libérons automatiquement. Fin.

mais toute application de longue durée ont tendance à avoir un état partagé. Ça peut être n'importe quoi, statique, Singleton... Souvent les applications non triviales ont tendance à faire des graphiques d'objets complexes. Juste oublier de définir une référence à null ou plus souvent en oubliant de retirer un objet d'une collection est assez pour faire une fuite de mémoire.

bien sûr toutes sortes d'auditeurs (comme les écouteurs UI), les caches, ou n'importe quel état partagé de longue durée ont tendance à produire une fuite de mémoire si elle n'est pas correctement traitée. Ce qu'il faut comprendre, c'est qu'il ne s'agit pas d'un boîtier Java corner, ni d'un problème avec le collecteur d'ordures. C'est un problème de conception. Nous concevons que nous ajoutons un auditeur à un objet de longue durée de vie, mais nous ne retirons pas l'auditeur quand il n'est plus nécessaire. Nous cachons des objets, mais nous n'avons aucune stratégie pour les retirer de la cache.

nous avons peut-être un graphe complexe qui stocke l'état précédent qui est nécessaire à un calcul. Mais l'état antérieur est elle-même liée à l'état d'avant et ainsi de suite.

comme nous devons fermer les connexions ou les 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 minuteries). Tous les objets qui permettent à un auditeur d'être notifié doivent fournir à la fois une méthode addListener et removeListener. Et quand ces les déclarants ne sont plus utilisés, ils doivent vider leur auditeur liste.

une fuite de mémoire est en effet vraiment possible et est parfaitement prévisible. Pas besoin de caractéristiques linguistiques spéciales ou de cas de coin. Les fuites de mémoire sont soit un indicateur que quelque chose manque peut-être ou même des problèmes de conception.

165
répondu Nicolas Bousquet 2018-02-13 07:27:47

la réponse dépend entièrement de ce que l'intervieweur pensait qu'ils demandaient.

Est-il possible, dans la pratique, pour rendre Java fuite? 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 ont été posées?

  • est-ce qu'une implémentation Java théoriquement "parfaite" est vulnérable aux fuites?
  • le candidat comprendre la différence entre la théorie et la réalité?
  • le candidat comprend-il comment fonctionne la collecte des ordures?
  • Ou comment la collecte des ordures est censé travailler, dans un cas idéal?
  • savent-ils qu'ils peuvent appeler d'autres langues via des interfaces natives?
  • savent-ils faire fuiter la mémoire dans ces autres langues?
  • est-ce que le candidat sait 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 la réponse que j'aurais pu utiliser dans cette situation d'entrevue". Et donc, je vais me concentrer sur les compétences d'interview au lieu de Java. Je crois que votre plus susceptible de répéter la situation de ne pas savoir la réponse à une question dans une interview que vous êtes d'être dans un endroit où le besoin de savoir comment faire Java leak. Donc, j'espère que cela va aider.

l'Un des plus les compétences importantes que vous pouvez développer pour les entrevues sont l'écoute active des questions et le travail avec l'intervieweur pour en extraire l'intention. Non seulement cela vous permet de répondre à leur question de la façon qu'ils veulent, mais montre également que vous avez des compétences essentielles de communication. Et quand il s'agit de choisir entre plusieurs développeurs tout aussi talentueux, j'engagerai celui qui écoute, pense et comprend avant qu'ils ne répondent à chaque fois.

138
répondu 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 les instances Connection , Statement et ResultSet avant de les rejeter ou de perdre les références à elles, au lieu de compter 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 sera restez ouvert, jusqu'à ce que le ramasseur d'ordures arrive et voit qu'il est inaccessible. GC va invoquer la méthode finalize , mais il y a des pilotes JDBC qui n'implémentent pas la finalize , du moins pas de la même manière que Connection.close est implémenté. Le comportement résultant est que tandis que la mémoire sera récupérée en raison d'objets inaccessibles étant collectées, les ressources (y compris la mémoire) associées à l'objet Connection pourrait tout simplement ne pas être récupéré.

In un tel événement où la méthode Connection 's finalize ne nettoie pas tout, on pourrait effectivement constater que la connexion physique au serveur de base de données durera plusieurs cycles de collecte des ordures, jusqu'à ce que le serveur de base de données finalement se rend compte que la connexion n'est pas vivante (si elle le fait), et devrait être fermée.

même si le pilote JDBC devait implémenter finalize , il est possible de lancer des exceptions lors de la finalisation. Le résultant le comportement est que toute mémoire associée à l'objet maintenant "dormant" ne sera pas récupérée, car finalize est garanti d'être invoqué qu'une seule fois.

le scénario ci - dessus de rencontrer des exceptions pendant la finalisation de l'objet est lié à un autre scénario qui pourrait éventuellement conduire à une fuite de la mémoire-résurrection de l'objet. La résurrection d'objet est souvent faite intentionnellement en créant une référence forte à l'objet d'être finalisé, à partir d'un autre objet. Lorsque la résurrection d'objet est mal utilisée, elle conduira à une fuite de mémoire en combinaison avec d'autres sources de fuites de mémoire.

Il ya beaucoup plus d'exemples que vous pouvez évoquer comme

  • gérer une instance List où vous ajoutez seulement à la liste et non en la supprimant (bien que vous devriez vous débarrasser des éléments dont vous n'avez plus besoin), ou
  • ouverture Socket s ou File s, mais non les fermer lorsqu'ils ne sont plus nécessaires (semblable à l'exemple ci-dessus concernant la classe Connection ).
  • ne pas décharger les Singletons lors de la suppression D'une application Java EE. Apparemment, le Classloader qui a chargé la classe singleton conservera une 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 à existe à cause du singleton.
117
répondu 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 la mise en œuvre 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'aviez implémenté vous-même, auriez-vous pensé à effacer l'élément array qui n'est plus utilisé ( elementData[--size] = null )? Cette référence pourrait garder un énorme objet en vie ...

106
répondu meriton 2015-09-01 04:50:05

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 Gérer les fuites de mémoire dans les programmes Java pour des exemples de la façon dont les fuites de mémoire se manifestent dans Java et ce que vous pouvez faire à ce sujet.

64
répondu Bill the Lizard 2011-06-24 16:18:07

vous êtes en mesure de faire fuite de mémoire avec soleil.misc.Classe dangereuse . 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 faire cela .

Code ne se compile pas dans Eclipse IDE - le compiler en utilisant la commande javac (pendant la compilation vous obtiendrez des mises en garde)

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
répondu stemm 2016-01-05 08:37:35

je peux copier ma réponse d'ici: la façon la plus facile de causer des fuites 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 de se produire. Il sera toujours en mesure de libérer les ressources. Dans les programmes avec gestion manuelle de la mémoire, c'est différent. Vous ne pouvez pas avoir un peu de mémoire en C en utilisant malloc (). Pour libérer la mémoire, vous avez besoin du pointeur que malloc retourné et appeler free() dessus. Mais si vous n'avez plus le pointeur (Oblitéré, ou vie dépassée), alors vous êtes malheureusement incapable de libérer cette mémoire et donc vous avez une fuite de mémoire.

Toutes les autres réponses sont dans mon définition, pas vraiment des fuites de mémoire. Ils visent tous à remplir la mémoire avec des trucs inutiles très vite. Mais à n'importe quel moment vous pourriez encore déréférencer les objets que vous avez créés et ainsi libérer la mémoire --> pas de fuite. la réponse d'acconrad est assez proche mais comme je dois admettre que sa solution est effectivement de" planter " le collecteur d'ordures en le forçant dans une boucle sans fin).

la réponse longue est: vous pouvez obtenir une fuite de mémoire en écrivant un bibliothèque pour Java en utilisant le JNI, qui peut avoir la gestion manuelle de la mémoire et donc avoir des fuites de mémoire. Si vous appelez cette bibliothèque, votre processus java laissera passer de la mémoire. Ou, vous pouvez avoir des bugs dans le JVM, de sorte que le JVM perd la mémoire. Il y a probablement des bugs dans la JVM, il peut même y en avoir quelques-uns connus puisque la collecte des ordures n'est pas si insignifiante, mais alors c'est toujours un bug. Par sa conception ce n'est pas possible. Vous demandez peut-être du code java qui est affecté par un tel bogue. Désolé, je ne sais pas l'un et il se pourrait bien que ce ne soit plus un bug dans la prochaine version Java de toute façon.

40
répondu 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 le substrat fait référence à la représentation interne de la chaîne originale, beaucoup plus longue, l'original reste en mémoire. Ainsi, aussi longtemps que vous avez une corde en jeu, vous avez toute la corde d'origine en mémoire, aussi, même si vous pourriez penser que vous vous accrochez à une corde d'un seul caractère.

le moyen d'éviter de stocker une référence non désirée à la chaîne originale est de faire quelque chose comme ceci:

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

pour la méchanceté ajoutée, vous pouvez aussi .intern() le substrat:

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

ainsi gardera en mémoire à la fois la chaîne longue originale et la chaîne dérivée même après que L'instance StringLeaker a été écartée.

33
répondu 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/component et de l'ajout d'un auditeur à un objet statique/application scoped et de ne pas supprimer l'auditeur lorsque le widget est détruit. Non seulement vous obtenez une fuite de mémoire, mais aussi un succès de performance comme quand quoi que vous écoutiez les incendies événements, tous vos anciens auditeurs sont appelés aussi.

33
répondu pauli 2016-02-16 15:40:24

prendre n'importe quelle application web courant dans n'importe quel conteneur de servlet (Tomcat, Jetty, Glassfish, n'importe quoi...). Redéployer l'application 10 ou 20 fois d'affilée (il peut suffire de simplement toucher la guerre dans le répertoire autodéploy du serveur.

sauf si quelqu'un a réellement testé ceci, les chances sont élevées que vous obtiendrez un OutOfMemoryError après quelques redéploiements, parce que l'application n'a pas pris soin de nettoyer après lui-même. Vous pouvez même trouver un bug dans votre serveur avec ce 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 du conteneur peut avoir des objets ou des classes de votre application peut être nettoyée.

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 ramassées.

Threads commencés par votre application, ThreadLocal variables, logging appenders sont quelques-uns des suspects habituels pour causer des fuites classloader.

32
répondu Harald Wellmann 2011-07-03 22:41:17

peut-être en utilisant un code natif externe via JNI?

avec du pur Java, c'est presque impossible.

mais c'est à propos d'un type "standard" de fuite de mémoire, quand vous ne pouvez plus accéder à la mémoire, mais il est toujours détenu par 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
répondu Rogach 2011-06-24 21:48:48

j'ai eu une belle "fuite de mémoire" en relation avec PermGen et XML parsing une fois. L'analyseur XML que nous avons utilisé (Je ne me souviens pas lequel c'était) a fait une chaîne de caractères.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 comme tagnames, donc nous avions un document comme:

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

en fait, ils n'ont pas utilisé de nombres mais des IDs textuels plus longs( environ 20 caractères), qui étaient uniques et sont venus à un taux de 10 à 15 millions par jour. Cela fait 200 MB de déchets par jour, qui n'est plus jamais nécessaire, et jamais Geded (puisqu'il est en PermGen). Nous avions le permgen réglé à 512 Mo, donc il a fallu environ deux jours pour que l'exception out-of-memory (OOME) arrive...

28
répondu Ron 2016-02-16 15:44:24

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

Log4j a ce mécanisme appelé contexte de Diagnostic emboîté (NDC) qui est un instrument pour distinguer la production de grumes intercalées de différentes sources. La granularité à laquelle NDC fonctionne est les threads, de sorte qu'il distingue les sorties de log de différents threads séparément.

afin de stocker des tags spécifiques au fil, la classe NDC de log4j utilise un Hashtable qui est saisi par L'objet Thread lui-même (par opposition à l'id thread), et ainsi jusqu'à ce que la balise NDC reste dans la mémoire tous les objets qui pendent de l'objet thread restent aussi dans la mémoire. Dans notre application web, nous utilisons NDC pour marquer les entrées avec un identifiant de requête afin de distinguer les journaux d'une seule requête séparément. Le conteneur qui associe la balise NDC avec un thread, la supprime également en retournant la réponse d'une requête. Le problème s'est produit lorsque, au cours de traiter une requête, 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é avec le thread inline qui a été généré. L'objet thread qui était la clé pour ce contexte NDC, est le thread inline qui a l'objet hugeList suspendu. Par conséquent, même après que le thread a 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
répondu Puneet 2014-03-07 21:29:39

j'ai pensé qu'il était intéressant que personne n'utilise les exemples de classe internes. Si vous avez une classe interne; il maintient intrinsèquement une référence à la classe conteneur. Bien sûr, il ne s'agit pas techniquement d'une fuite de mémoire car Java finira par la nettoyer, mais cela peut faire traîner les classes 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 que vous obtenez un Example2 en rejetant Example1, vous aurez naturellement toujours un lien vers un Example1 objet.

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 également entendu une rumeur que si vous avez une variable qui existe pour plus de temps qu'un certain nombre de temps; Java suppose qu'elle existera toujours et ne tentera jamais réellement de la nettoyer si ne peut plus être atteint en code. Mais c'est totalement infondées.

21
répondu Suroot 2011-07-09 01:23:55

qu'est-ce qu'une fuite de mémoire:

  • il est causé par un bug ou mauvaise conception.
  • C'est une perte de mémoire.
  • ça empire avec le temps.
  • le ramasseur d'ordures ne peut pas le nettoyer.

exemple type:

A 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 (garde seulement les objets récemment utilisés en cache).

bien Sûr, vous pouvez rendre les choses beaucoup plus compliquées:

  • utilisant constructions Threadlocales .
  • ajouter plus arbres de référence complexes .
  • ou des fuites provoquées par des de la 3ème partie des bibliothèques .

Ce qui arrive souvent:

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

20
répondu bvdb 2014-10-28 13:18:50

créez une carte statique et ajoutez des références à celle-ci. Ils 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
répondu 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 de finalisation de cette classe. Points Bonus si le finalizer crée plusieurs instances. Voici un programme simple qui fuit tout le tas entre quelques secondes et quelques minutes selon 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
répondu sethobrien 2011-07-22 08:05:07

l'intervieweur était probablement à la recherche d'une référence circulaire comme le code ci-dessous (qui, soit dit en passant, ne fuit la mémoire que dans des JVM très anciennes qui utilisaient le comptage de référence, ce qui n'est plus le cas). Mais c'est une question assez vague, donc c'est 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 */
}

alors vous pouvez expliquer qu'avec le comptage de référence, le code ci-dessus aurait une fuite de mémoire. Mais la plupart des JVM modernes n'utilisent pas le comptage de référence plus longtemps, la plupart utilisent un collecteur de déchets de balayage, qui recueillera en fait cette mémoire.

ensuite, vous pourriez 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. */
    }
}

alors 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 attribuant des ressources natives sous-jacentes, qui n'ont pas été libérées par votre code Java.

à la fin de nos jours, avec une JVM moderne, vous devez écrire un code Java qui alloue une ressource native en dehors de la portée normale de la conscience de la JVM.

16
répondu 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 méthode native, appeler malloc . N'appelez pas free .
  3. appelez la méthode native.

rappelez-vous, les allocations de mémoire en code natif proviennent du tas JVM.

15
répondu Paul Morie 2011-07-21 20:52:40

je suis tombé sur un type plus subtil de fuite de Ressources récemment. Nous ouvrons les ressources via getresourceasstream de class loader et il s'est avéré que les poignées de flux d'entrée n'étaient pas fermées.

euh, vous pourriez dire, quel idiot.

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

Tous vous avez besoin est un fichier jar avec un fichier à l'intérieur qui être référencé à partir du code Java. Plus le fichier jar est gros, plus la mémoire est rapidement 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();
    }
}

il suffit de coller 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 seconde 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 nettoyés immédiatement et donc, ne contribuent pas à la taille du segment. 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 peu importe la taille des itérations que vous avez choisies dans l'exemple ci-dessus. La consommation de mémoire de votre processus (visible en haut (RES/RSS) ou process explorer) augmente à moins que l'application n'arrive à la commande wait. Dans la configuration ci-dessus, il allouera environ 150 Mo en mémoire.

si vous voulez que l'application joue en toute sécurité, fermez le flux d'entrée où il a été créé:

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

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

assez simple et surprenant.

15
répondu Jay 2012-09-12 20:07:37

Je ne pense pas que quelqu'un ait dit cela encore: vous pouvez ressusciter un objet en écrasant la méthode finalize() telle 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
répondu Ben 2011-07-03 17:36:58

comme beaucoup de gens l'ont suggéré, les fuites de ressources sont assez faciles à causer - comme les exemples JDBC. Les fuites de mémoire réelles sont un peu plus difficiles - surtout si vous ne comptez pas sur les morceaux cassés de la JVM 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 de vraies fuites de mémoire non plus. Si rien ne peut y accéder, alors ce sera des ordures collectées, et si quelque chose peut y accéder, alors c'est pas de fuite...

une façon que a utilisé pour travailler cependant - et je ne sais pas si elle le fait encore - est d'avoir une chaîne circulaire de trois profondeurs. Comme dans L'objet a il y 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 récupérée en toute sécurité si A et B ne sont pas accessibles par autre chose, mais ne pouvait pas gérer la chaîne à trois voies...

14
répondu Graham 2011-07-21 17:55:51

il y a de nombreuses situations différentes où la mémoire fuit. J'en ai rencontré une, 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
répondu Ben Xu 2011-07-10 07:46:03

fils ne sont pas recueillis jusqu'à ce qu'ils se terminent. Ils servent de roots de collecte des ordures. Ils sont l'un des rares objets qui ne seront pas récupérés simplement par les oublier ou de compensation des références.

considérer: le motif de base pour terminer un thread ouvrier est de définir une variable de condition vu par le thread. Le thread peut vérifier la variable périodiquement et l'utiliser comme un signal pour terminer. Si la variable n'est pas déclaré volatile , alors le changement de la variable pourrait ne pas être vu par le thread, donc il ne saura pas se terminer. Ou imaginez si certains threads veulent mettre à jour un objet partagé, mais bloquent 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, alors les threads obsolètes/collés pourraient ne pas être remarqués, et s'accumuler indéfiniment, provoquant une fuite de mémoire. Les Threads sont susceptibles d'utiliser d'autres données dans votre application, donc empêchera également tout ce qu'ils référence directement d'être jamais collectés.

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

Appel System.gc() tout ce que vous voulez, mais à l'objet passé en leakMe ne mourra jamais.

(*édité*)

11
répondu Boann 2017-05-23 12:02:58

je pense qu'un exemple valable pourrait être l'utilisation de variables ThreadLocal dans un environnement où les threads sont mis en commun.

par exemple, en utilisant des variables ThreadLocal dans les Servlets pour communiquer avec d'autres composants web, ayant les threads étant créés par le conteneur et en maintenant 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 écrive leurs valeurs.

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

9
répondu mschonaker 2011-07-03 06:49:51

l'intervieweur aurait pu être à 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 avec le comptage de référence des éboueurs. Vous expliquerez alors poliment que les JVM utilisent un algorithme beaucoup plus sophistiqué qui n'a pas cette limitation.

- Wes Tarle

9
répondu Wesley Tarle 2011-07-04 15:06:58