web-dev-qa-db-fra.com

Le moyen le plus simple de provoquer une fuite de mémoire en Java?

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

Quel est le moyen le plus simple de provoquer une fuite de mémoire Java?

30
Paul McKenzie

Vous ne pouvez pas vraiment "perdre de la mémoire" en Java à moins de:

  • chaînes de stagiaire
  • générer des classes
  • fuite de mémoire dans le code natif appelé par jni
  • conservez des références à des choses que vous ne voulez pas dans un endroit oublié ou obscur.

Je suppose que vous êtes intéressé par le dernier cas. Les scénarios courants sont les suivants:

  • les auditeurs, surtout avec les classes internes
  • caches.

Un bon exemple serait de:

  • construire un gui Swing qui lance un nombre potentiellement illimité de fenêtres modales;
  • demandez à la fenêtre modale de faire quelque chose comme ça lors de son initialisation:
    StaticGuiHelper.getMainApplicationFrame().getOneOfTheButtons().addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent e){
         // do nothing...
      }
    })
    

L'action enregistrée ne fait rien, mais la fenêtre modale restera en mémoire pour toujours, même après sa fermeture, ce qui provoquera une fuite. En effet, les écouteurs ne sont jamais annulés et chaque objet de classe interne anonyme contient une référence (invisible) à son objet externe . De plus, tout objet référencé à partir des fenêtres modales a également une chance de fuir.

C'est pourquoi des bibliothèques telles que EventBus utilisent des références faibles par défaut.

Outre les auditeurs, d'autres exemples typiques sont les caches, mais je ne peux pas penser à un exemple de Nice. 

29
fdreger

"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 ne sont 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. La réponse d’acconrad est assez proche, mais je dois admettre que sa solution est de simplement "écraser" le récupérateur de mémoire 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 de 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 possible que ce ne soit plus un bogue dans la prochaine version de Java.

15
yankee

Voici un exemple simple

public class finalizer {
    @Override
        protected void finalize() throws Throwable {
        while (true) {
            Thread.yield();
        }
    }

    public static void main(String[] args) {
        while (true) {
            for (int i = 0; i < 100000; i++) {
                finalizer f = new finalizer();
            }

            System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!");
        }
    }
}
11
acconrad
public static List<byte[]> list = new ArrayList<byte[]>();

Et ajoutez ensuite de (grands) tableaux sans les supprimer. À un moment donné, vous manquerez de mémoire sans vous en douter. (Vous pouvez le faire avec n’importe quel objet, mais avec de grands tableaux complets, vous pouvez manquer de mémoire plus rapidement)

En Java, si vous déréférencez un objet (il tombe hors de la portée), il est collecté. Vous devez donc garder une référence pour avoir un problème de mémoire.

8
Bozho
  1. Créer une collection d'objets à la portée de la classe 
  2. Ajouter périodiquement de nouveaux objets à la collection 
  3. Ne supprimez pas la référence à l'instance de la classe qui contient la collection.

Comme il y a toujours une référence à la collection et l'instance de l'objet propriétaire de la collection, le récupérateur de place ne nettoiera jamais cette mémoire, ce qui provoque une "fuite" au fil du temps.

3
Paul Sasik

D'après ce que j'ai lu dans la réponse la plus votée, vous demandez probablement une fuite de mémoire de type C. Eh bien, puisqu'il existe une collection garbagge, vous ne pouvez pas allouer un objet, perdre toutes ses références et le conserver en occupant encore de la mémoire - ce serait un grave bogue de la JVM.

D'autre part, il peut arriver que des threads fuient - ce qui, bien sûr, provoquerait cet état, car un thread serait en cours d'exécution avec ses références aux objets et vous risqueriez de perdre sa référence. Vous pouvez toujours obtenir la référence de fil via l'API - http://www.exampledepot.com/egs/Java.lang/ListThreads.html

3
Ondra Žižka

La classe Box extrêmement artificielle suivante perdra de la mémoire si elle est utilisée. Les objets qui sont put dans cette classe sont finalement (après un autre appel à put pour être précis ... à condition que le même objet ne soit pas re -put.) Inaccessibles au monde extérieur. Ils ne peuvent pas être déréférencés via cette classe, mais cette classe garantit qu'ils ne peuvent pas être collectés. C'est une fuite réelle. Je sais que cela est vraiment artificiel, mais des cas similaires sont possibles par accident.

import Java.util.ArrayList;
import Java.util.Collection;
import Java.util.Stack;

public class Box <E> {
    private final Collection<Box<?>> createdBoxes = new ArrayList<Box<?>>();
    private final Stack<E> stack = new Stack<E>();

    public Box () {
        createdBoxes.add(this);
    }

    public void put (E e) {
        stack.Push(e);
    }

    public E get () {
        if (stack.isEmpty()) {
            return null;
        }
        return stack.peek();
    }
}
1
Thomas Eding

Il semble que la plupart des réponses ne soient pas des fuites de mémoire de style C.

J'ai pensé ajouter un exemple de classe de bibliothèque avec un bogue qui vous donnerait une exception de mémoire insuffisante. Encore une fois, il ne s'agit pas d'une véritable fuite de mémoire, mais d'un exemple de manque de mémoire inattendu.

public class Scratch {
    public static void main(String[] args) throws Exception {
        long lastOut = System.currentTimeMillis();
        File file = new File("deleteme.txt");

        ObjectOutputStream out;
        try {
            out = new ObjectOutputStream(
                    new FileOutputStream("deleteme.txt"));

            while (true) {
                out.writeUnshared(new LittleObject());
                if ((System.currentTimeMillis() - lastOut) > 2000) {
                    lastOut = System.currentTimeMillis();
                    System.out.println("Size " + file.length());
                    // out.reset();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class LittleObject implements Serializable {
    int x = 0;
}

Vous trouverez le code original et la description du bogue à l'adresse 

http://bugs.Sun.com/bugdatabase/view_bug.do?bug_id=4363937

0
MrJacqes

Essayez cette classe simple:

public class Memory {
private Map<String, List<Object>> dontGarbageMe = new HashMap<String, List<Object>>();

public Memory() {
    dontGarbageMe.put("map", new ArrayList<Object>());
}

public void useMemInMB(long size) {
    System.out.println("Before=" + getFreeMemInMB() + " MB");

    long before = getFreeMemInMB();
    while ((before  - getFreeMemInMB()) < size) { 
        dontGarbageMe.get("map").add("aaaaaaaaaaaaaaaaaaaaaa");
    }

    dontGarbageMe.put("map", null);

    System.out.println("After=" + getFreeMemInMB() + " MB");
}

private long getFreeMemInMB() {
    return Runtime.getRuntime().freeMemory() / (1024 * 1024);
}

public static void main(String[] args) {
    Memory m = new Memory();
    m.useMemInMB(15);  // put here apropriate huge value
}
}
0
smas