2010-07-09 4 views
8

Je souhaite utiliser MapMaker pour créer une carte mettant en cache des objets volumineux, , qui doivent être supprimés du cache si la mémoire est insuffisante. Ce petit programme de démonstration semble fonctionner très bien:Utilisation de MapMaker pour créer un cache

public class TestValue { 
    private final int id; 
    private final int[] data = new int[100000]; 

    public TestValue(int id) { 
     this.id = id; 
    } 

    @Override 
    protected void finalize() throws Throwable { 
     super.finalize(); 
     System.out.println("finalized"); 
    } 
} 


public class Main { 

    private ConcurrentMap<Integer, TestValue> cache; 
    MemoryMXBean memoryBean; 

    public Main() { 
     cache = new MapMaker() 
       .weakKeys() 
       .softValues() 
       .makeMap(); 
     memoryBean = ManagementFactory.getMemoryMXBean(); 
    } 

    public void test() { 
     int i = 0; 
     while (true) { 
      System.out.println("Etntries: " + cache.size() + " heap: " 
       + memoryBean.getHeapMemoryUsage() + " non-heap: " 
       + memoryBean.getNonHeapMemoryUsage()); 
      for (int j = 0; j < 10; j++) { 
       i++; 
       TestValue t = new TestValue(i); 
       cache.put(i, t); 
      } 
      try { 
       Thread.sleep(100); 
      } catch (InterruptedException ex) { 
      } 
     } 
    } 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) { 
     Main m = new Main(); 
     m.test(); 
    } 

} 

Cependant, quand je fais la même chose dans mon application réelle, les entrées sont essentiellement retirées de la cache dès qu'ils sont ajoutés. Dans mon application réelle , j'utilise également des entiers comme clés, et les valeurs mises en cache sont blocs d'archive lus à partir du disque qui contient des données. Autant que je le comprends, les références faibles sont ramassées à la poubelle dès qu'elles sont n'est plus utilisé, donc cela semble logique car les clés sont faibles références . Si je crée la carte comme ceci:

data = new MapMaker() 
      .softValues() 
      .makeMap(); 

Les entrées ne sont jamais et ramasse-miettes je reçois une erreur hors de la mémoire dans mon programme de test. La méthode finalize sur les entrées TestValue n'est jamais appelée. Si je change la méthode d'essai à ce qui suit:

public void test() { 
    int i = 0; 
    while (true) { 
     for (final Entry<Integer, TestValue> entry : 
      data.entrySet()) { 
      if (entry.getValue() == null) { 
       data.remove(entry.getKey()); 
      } 
     } 
     System.out.println("Etntries: " + data.size() + " heap: " 
      + memoryBean.getHeapMemoryUsage() + " non-heap: " 
      + memoryBean.getNonHeapMemoryUsage()); 
     for (int j = 0; j < 10; j++) { 
      i++; 
      TestValue t = new TestValue(i); 
      data.put(i, t); 
     } 
     try { 
      Thread.sleep(100); 
     } catch (InterruptedException ex) { 
     } 
    } 
} 

entrées sont supprimées du cache et le finaliseur sur les objets TestValue est appelé, mais après un certain temps je reçois aussi un hors-mémoire erreur . Donc, ma question est: quelle est la bonne façon d'utiliser MapMaker pour créer une carte qui peut être utilisée comme cache? Pourquoi mon programme de test ne supprime-t-il pas les entrées dès que possible si j'utilise weakKeys? Est-il possible de ajouter une file d'attente de référence à la carte de cache?

+0

quelqu'un peut-il modifier le code pour le rendre plus facile à lire? – nanda

+0

Je suis un peu surpris par cela. J'ai utilisé 'softValues' exactement de la même manière et cela a bien fonctionné, avec' SoftReference' étant effacé quand la mémoire est faible. – finnw

Répondre

3

Les touches faibles semblent être une erreur. Essayez d'utiliser des clés fortes car ce sont des entiers.

+0

J'ai essayé cela et cela fonctionne si j'appelle System.gc() avant de créer un nouvel objet et de l'ajouter au cache. Si je ne fais pas cela, je reçois une exception de mémoire insuffisante tôt ou tard. Est-ce la bonne approche ou recommandez-vous autre chose? – Michael

+0

Vous avez deux versions de TestValue, une contient un grand tableau et une seule contient un int. Testez-vous avec le grand tableau? Sinon, il est possible que le GC ne puisse pas libérer suffisamment de mémoire. –

8

Il y a beaucoup de choses qui peuvent se passer, mais en ce qui concerne votre programme de test utilisant des valeurs logicielles: vous pouvez obtenir OutOfMemoryError même si vous avez des SoftReferences qui n'ont pas encore été récupérées. Cela vaut la peine d'être répété: vous pouvez obtenir une OutOfMemoryError même si vous avez des SoftReferences qui n'ont pas encore été effacées. Les références SoftReferences sont un peu étranges, voir http://jeremymanson.blogspot.com/2009/07/how-hotspot-decides-to-clear_07.html pour une description de la mécanique actuelle. Probablement dans votre test, le GC n'a tout simplement pas eu le temps de faire deux GC complets. Lorsque vous utilisiez weakKeys, le CG les effaçait immédiatement et n'attendait pas une pause GC complète. (WeakReferences b/c sont collectées de manière agressive.)

À mon avis, si vous voulez un cache sensible à la mémoire avec les touches entières, je pense que ce qui suit est approprié:

data = new MapMaker().softValues().makeMap(); 

Vous pouvez facilement faire un programme de test qui lance OutOfMemoryError, mais si votre application réelle est assez bien conduite et ne subit pas trop de pression, vous pourriez être OK. Les SoftReferences sont assez difficiles à obtenir.

Si vous avez besoin d'utiliser System.gc() pour éviter la mémoire insuffisante, je vous recommande plutôt de passer à une carte LRU avec une taille max fixe (voir le javadoc de java.util.LinkedHashMap pour un exemple. Ce n'est pas simultané, mais je m'attends à ce que cela vous donne un meilleur débit à la fin que de demander au système de faire une collecte de temps pleine pause avec un tas de temps supplémentaires.Oh, et une note finale sur les clés entières et weakKeys(): MapMaker utilise la comparaison d'identité pour les clés lors de l'utilisation de touches faibles ou soft, et c'est assez difficile à faire correctement. Témoin ce qui suit:

Map<Integer,String> map = new MapMaker().weakKeys().makeMap(); 
Integer a = new Integer(1); 
Integer b = new Integer(1); 
Integer c = 1; //auto box 
Integer d = 1; //auto box 
map.put(a, "A"); 
map.put(b, "B"); 
map.put(c,"C"); 
map.put(d,"D"); 
map.size() // size is 3; 

Bonne chance.

+0

+1 parce que je ne me suis pas rendu compte que vous pouvez obtenir une OutOfMemoryError avant que toutes les SoftReferences ne soient gcées. –

Questions connexes