2010-07-09 19 views
8

quiero utilizar Map Maker para crear un mapa que almacena en caché los objetos de gran tamaño, que debe ser eliminado de la caché si no hay suficiente memoria. Este pequeño programa de demostración parece funcionar bien:El uso de Map Maker para crear una memoria caché

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

} 

Sin embargo, cuando lo haga lo mismo en mi aplicación real, las entradas son básicamente eliminado de la caché tan pronto como se abonen. En mi aplicación real , también utilizo como teclas de números enteros, y los valores son almacenados en caché de archivos bloques leídos del disco que contiene algunos datos. Por lo que yo entiendo , débiles son las referencias recolección de basura tan pronto como estén ya no se usa, así que esto parece tener sentido ya que las claves son débiles referencias. Si creo el mapa de esta manera:

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

Las entradas son no recolección de basura y me sale un error de falta de memoria en mi programa de prueba. El método de finalización en las entradas TestValue nunca se llama. Si cambio el método de ensayo a lo siguiente:

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

entradas se eliminan de la memoria caché y el finalizador de los TestValue objetos que se llama, pero después de un tiempo también reciben un out-of-memoria de errores .

Así que mi pregunta es: ¿cuál es la forma correcta de utilizar MapMaker para crear un mapa que se puede utilizar como un caché? ¿Por qué mi programa de prueba no elimina las entradas lo antes posible si uso weakKeys? ¿Es posible agregar una cola de referencia al mapa de caché?

+0

alguien puede editar el código para que sea más fácil de leer? – nanda

+0

Estoy un poco sorprendido por esto. He usado 'softValues' exactamente de la misma manera y funcionó bien, con el' SoftReference's borrado cuando la memoria se agota. – finnw

Respuesta

3

claves débiles parece un error. Intenta usar claves fuertes ya que son números enteros.

+0

Lo intenté y funciona si llamo a System.gc() antes de crear un nuevo objeto y agregarlo al caché. Si no hago esto, tarde o temprano me sale una excepción de falta de memoria. ¿Es este el enfoque correcto o recomiendas algo más? – Michael

+0

Tiene dos versiones de TestValue, una contiene una gran matriz y una solo contiene una int. ¿Estás probando con la gran matriz? De lo contrario, es posible que el GC simplemente no pueda liberar suficiente memoria. –

8

Hay muchas cosas que pueden estar sucediendo, pero con respecto a su programa de prueba que usa valores variables: puede obtener OutOfMemoryError incluso si tiene SoftReferences que aún no han sido recolectados. Eso lleva a repetir: puede obtener un OutOfMemoryError incluso si tiene SoftReferences que aún no se han borrado.

SoftReferences son un poco raro, ver http://jeremymanson.blogspot.com/2009/07/how-hotspot-decides-to-clear_07.html para una descripción de la mecánica actual. Probablemente en su caso de prueba, el GC simplemente no tuvo tiempo de hacer dos GC completos.

cuando lo estaba utilizando weakKeys, el CG ellos aclarado de inmediato, y no tiene que esperar a una pausa GC completa. (WeakReferences B/C se recogen de forma agresiva.)

En mi opinión, si quieres un caché de memoria sensible con claves enteras, pensaría que lo que sigue es apropiado:

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

Usted puede hacer un programa de prueba que arroja OutOfMemoryError, pero si su aplicación real se comporta bien y no se somete a demasiada presión, puede estar bien. SoftReferences es bastante difícil de hacer bien.

Si necesita utilizar System.gc(), evite la falta de memoria, en su lugar, le recomendaría cambiar a un mapa LRU con un tamaño máximo fijo (consulte el javadoc de java.util.LinkedHashMap para ver un ejemplo).) No es concurrente, pero espero que al final le proporcione una mejor capacidad de procesamiento que pedirle al sistema que haga una recolección de basura completa en un montón de veces adicionales.

Ah, y una nota final sobre las claves enteras y weakKeys(): MapMaker utiliza la comparación de identidad para las teclas cuando se utilizan teclas débiles o suaves, y eso es bastante difícil de hacer correctamente. Sea testigo de lo siguiente:

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; 

Buena suerte.

+0

+1 porque no me di cuenta de que puedes obtener un OutOfMemoryError antes de que todas las SoftReferences sean compiladas. –

Cuestiones relacionadas