2010-01-13 17 views
5

A continuación se muestra una pequeña prueba que he codificado para educarme en API de referencias. Pensé que esto nunca arrojaría a OOME, pero está lanzándolo. No puedo entender por qué. agradecer cualquier ayuda en esto.OutOfMemoryErrors incluso después de usar WeakReference para claves y valores

public static void main(String[] args) 
{ 
    Map<WeakReference<Long>, WeakReference<Double>> weak = new HashMap<WeakReference<Long>, WeakReference<Double>>(500000, 1.0f); 

    ReferenceQueue<Long> keyRefQ = new ReferenceQueue<Long>(); 
    ReferenceQueue<Double> valueRefQ = new ReferenceQueue<Double>(); 

    int totalClearedKeys = 0; 
    int totalClearedValues = 0; 

    for (long putCount = 0; putCount <= Long.MAX_VALUE; putCount += 100000) 
    { 
     weak(weak, keyRefQ, valueRefQ, 100000); 

     totalClearedKeys += poll(keyRefQ); 
     totalClearedValues += poll(valueRefQ); 

     System.out.println("Total PUTs so far = " + putCount); 
     System.out.println("Total KEYs CLEARED so far = " + totalClearedKeys); 
     System.out.println("Total VALUESs CLEARED so far = " + totalClearedValues); 
    } 

} 

public static void weak(Map<WeakReference<Long>, WeakReference<Double>> m, ReferenceQueue<Long> keyRefQ, 
     ReferenceQueue<Double> valueRefQ, long limit) 
{ 
    for (long i = 1; i <= limit; i++) 
    { 
     m.put(new WeakReference<Long>(new Long(i), keyRefQ), new WeakReference<Double>(new Double(i), valueRefQ)); 
     long heapFreeSize = Runtime.getRuntime().freeMemory(); 
     if (i % 100000 == 0) 
     { 
      System.out.println(i); 
      System.out.println(heapFreeSize/131072 + "MB"); 
      System.out.println(); 
     } 

    } 
} 

private static int poll(ReferenceQueue<?> keyRefQ) 
{ 
    Reference<?> poll = keyRefQ.poll(); 
    int i = 0; 
    while (poll != null) 
    { 
     // 
     poll.clear(); 
     poll = keyRefQ.poll(); 
     i++; 
    } 
    return i; 

} 

}

y abajo es el registro cuando corrió con 64MB del montón

Total PUTs so far = 0 
Total KEYs CLEARED so far = 77982 
Total VALUESs CLEARED so far = 77980 
100000 
24MB 

Total PUTs so far = 100000 
Total KEYs CLEARED so far = 134616 
Total VALUESs CLEARED so far = 134614 
100000 
53MB 

Total PUTs so far = 200000 
Total KEYs CLEARED so far = 221489 
Total VALUESs CLEARED so far = 221488 
100000 
157MB 

Total PUTs so far = 300000 
Total KEYs CLEARED so far = 366966 
Total VALUESs CLEARED so far = 366966 
100000 
77MB 

Total PUTs so far = 400000 
Total KEYs CLEARED so far = 366968 
Total VALUESs CLEARED so far = 366967 
100000 
129MB 

Total PUTs so far = 500000 
Total KEYs CLEARED so far = 533883 
Total VALUESs CLEARED so far = 533881 
100000 
50MB 

Total PUTs so far = 600000 
Total KEYs CLEARED so far = 533886 
Total VALUESs CLEARED so far = 533883 
100000 
6MB 

Total PUTs so far = 700000 
Total KEYs CLEARED so far = 775763 
Total VALUESs CLEARED so far = 775762 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at Referencestest.weak(Referencestest.java:38) 
    at Referencestest.main(Referencestest.java:21) 
+0

'heapFreeSize/131072 +" MB "' - un cálculo inusual. –

+0

es solo una estimación rápida y sucia basada en http://www.matisse.net/bitcalc/ –

+0

megabit? ¿No sería megabyte una unidad más apropiada? :) – falstro

Respuesta

3

El corazón del problema es probablemente que usted está llenando su montón con WeakReference-objetos, las referencias débiles se borran cuando se está quedando sin memoria, pero los objetos de referencia en sí no son, por lo que su HashMap se está llenando con carga de bote si los objetos WeakReference (sin mencionar la matriz de objetos que utiliza la hashmap, que crecerá indefinidamente), todos señalando nulo.

La solución, como ya se señaló, es un hashmap débil, que borrará esos objetos si ya no están en uso (esto se hace durante el lanzamiento).

EDIT:
Como señaló Kevin, que ya tiene su lógica de referencia cola de desechos (que no pagar suficiente atención), una solución utilizando su código es apenas claro él fuera del mapa en el punto donde la clave ha sido recolectada. Así es exactamente como funciona el mapa de hash débil (donde el sondeo simplemente se activa en el inserto).

+0

Marqué esto porque soluciona el problema, pero la solución realmente no es un 'WeakHashMap'. Como ya ha codificado toda la lógica de sondeo, debería agregar código para eliminar los objetos 'WeakHashReference' del' Mapa' que ya no hacen referencia a ningún objeto real. –

+0

@Kevin Brock: Estás en lo cierto. No presté suficiente atención. Gracias, incorporaré eso. – falstro

4

de http://weblogs.java.net/blog/2006/05/04/understanding-weak-references

Creo que su uso de HashMap es probable que sea el tema. Es posible que desee utilizar WeakHashMap

Para resolver el "widget número de serie" problema anterior, la cosa más fácil de hacer es utilizar la clase incorporada WeakHashMap. WeakHashMap funciona exactamente igual que HashMap, excepto que se hace referencia a las claves (¡no a los valores !) Usando referencias débiles . Si una clave WeakHashMap se convierte en basura, su entrada se elimina automáticamente . Esto evita las trampas que describí y no requiere cambios que no sean el cambio de HashMap a WeakHashMap. Si usted es siguiendo la convención estándar de haciendo referencia a sus mapas a través de la interfaz Map , ningún otro código necesita igualar tenga en cuenta el cambio.

+0

También existe el problema potencial de que las referencias no se pongan en cola inmediatamente después de ser encontradas por el GC. –

+0

He pasado por ese artículo antes de codificar este programa ... y sé que esto se puede arreglar usando WeakHashMap ... estoy tratando de entender y aprender la API de referencia ... así que publiqué la pregunta aquí ... tx de todos modos –

+0

¿Qué está enquistando? –

0

No soy un experto en Java, pero sé que en .NET cuando hace una gran cantidad de asignación de memoria de objetos puede obtener fragmentación de pila hasta el punto en que solo hay porciones pequeñas de memoria contigua disponibles para su asignación aunque mucha más memoria aparece como "libre".

Una búsqueda rápida en Google en "java heap fragmentation" trae aparejado un resultado aparentemente relevante aunque no los he visto bien.

+0

Java puede 'desfragmentar' el montón si es necesario, generalmente llamado 'compactación'. – falstro

+0

¿La compactación realmente ocurre cuando se llama o simplemente notifica al recolector de basura que debe producirse la compactación? De nuevo, volviendo a .NET cuando, por ejemplo, intenta forzar la recolección de basura, no es necesario forzar una recolección para que ocurra inmediatamente. Entiendo que la recolección y compactación de basura son diferentes, solo me pregunto si la compactación sigue el mismo tipo de procedimiento "Voy a hacer compactación cuando tengo el tiempo". –

+0

es parte del algoritmo GC de java "Mark-Sweep-Compact" ... así que cuando se invoca un GC, entonces la compactación también ocurre –

1

Incluso cuando sus débiles referencias dejan de lado las cosas a las que se refieren, aún así no se reciclan.

Por lo tanto, eventualmente su hash se llenará de referencias a nada y se bloqueará.

Lo que necesitarías (si quisieras hacerlo de esta forma) sería tener un evento desencadenado por la eliminación de objetos que entró y eliminó la referencia del hash. (lo que podría causar problemas de subprocesamiento que debe tener en cuenta también)

+0

tx para confirmar en mi comprensión –

0

Otro problema puede ser que Java, por alguna razón, no siempre activa su garbinge collecter cuando se está quedando sin memoria, por lo que es posible que tenga que insertar explícitamente llamadas para activar el recolector Pruebe algo como

if ((putCount% 1000) === 0) Runtime.getRuntime(). Gc();

en su bucle.

Editar: Parece que las nuevas implementaciones de Java de Sun ahora funciona la llamada en el colector garbadge antes de tirar OutOfMemmoryException, pero estoy bastante seguro de que el siguiente programa tiraría OutOfMemmoryException con jre1.3 o 1.4

public class Prueba { public static void main (String args []) { while (true) { byte [] data = new byte [1000000]; } } }

+0

Pensé que el GC siempre se invoca si es necesario antes de terminar la JVM con OOM ... es mi entendimiento correcto –

+0

Llamar a gc no necesariamente fuerza la recolección de basura. La mayoría de las veces es posible, pero no está garantizado. –

+0

la JVM siempre intentará un GC si no puede asignar más memoria. Activar un GC solo es útil si actualmente está inactivo y desea usar los ciclos de repuesto para limpiar. – falstro

0

Otros han señalado correctamente cuál es el problema; p.ej. @roe, @Bill K.

Pero otra forma de resolver este tipo de problema (aparte de rascarse la cabeza, preguntar por SO, etc.), es mirar y ver cómo funciona el enfoque recomendado por Sun. En este caso, puede encontrarlo en el código fuente de la clase WeakHashMap.

Hay algunas maneras de encontrar el código fuente de Java:

  • Si usted tiene un buen IDE Java para correr, debería ser capaz de mostrar el código fuente de cualquier clase en la biblioteca de clases.

  • La mayoría de las descargas de J2SE JDK incluyen archivos JAR de origen para (al menos) las clases API públicas.

  • Puede descargar específicamente las distribuciones de fuente completa para las versiones de Java basadas en OpenJDK.

Sin embargo, el enfoque de cero esfuerzo es hacer una búsqueda en Google, utilizando el nombre completo de la clase con ".java.html" clavado en el extremo. Por ejemplo, buscar "java.util.WeakHashMap.java.html" da this link en la primera página de resultados de búsqueda.

Y la fuente le dirá que la implementación estándar de WeakHashMap sondea explícitamente su cola de referencia para borrar las referencias débiles obsoletas (es decir, rotas) del conjunto de claves del mapa. De hecho, lo hace cada vez que accede o actualiza el mapa, o simplemente solicita su tamaño.

Cuestiones relacionadas