Pregunta

Estoy intentando insertar unos 50,000 objetos (y por lo tanto 50,000 claves) en un java.util.HashMap<java.awt.Point, Segment>. Sin embargo, sigo recibiendo una excepción OutOfMemory. (Segment es mi propia clase, muy ligera, un String campo y 3 int campos).

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.HashMap.resize(HashMap.java:508)
    at java.util.HashMap.addEntry(HashMap.java:799)
    at java.util.HashMap.put(HashMap.java:431)
    at bus.tools.UpdateMap.putSegment(UpdateMap.java:168)

Esto parece bastante ridículo ya que veo que hay mucha memoria disponible en la máquina, tanto en RAM libre como en espacio HD para memoria virtual.

¿Es posible que Java se esté ejecutando con algunos requisitos de memoria estrictos? ¿Puedo aumentar estos?

¿Hay alguna limitación extraña con HashMap? ¿Voy a tener que implementar el mío? ¿Hay otras clases que valga la pena mirar?

(Estoy ejecutando Java 5 bajo OS X 10.5 en una máquina Intel con 2GB de RAM.)

¿Fue útil?

Solución

Puede aumentar el tamaño máximo de almacenamiento dinámico pasando -Xmx128m (donde 128 es el número de megabytes) a Java. No recuerdo el tamaño predeterminado, pero me parece que era algo bastante pequeño.

Puede verificar mediante programación la cantidad de memoria disponible utilizando Clase de tiempo de ejecución .

// Get current size of heap in bytes
long heapSize = Runtime.getRuntime().totalMemory();

// Get maximum size of heap in bytes. The heap cannot grow beyond this size.
// Any attempt will result in an OutOfMemoryException.
long heapMaxSize = Runtime.getRuntime().maxMemory();

// Get amount of free memory within the heap in bytes. This size will increase
// after garbage collection and decrease as new objects are created.
long heapFreeSize = Runtime.getRuntime().freeMemory();

(Ejemplo de Java Developers Almanac )

Esto también se aborda parcialmente en Preguntas frecuentes sobre Java HotSpot VM y en la página Java 6 GC Tuning .

Otros consejos

Algunas personas sugieren cambiar los parámetros de HashMap para ajustar los requisitos de memoria. Sugeriría medir en lugar de adivinar ; podría ser algo más que causa el OOME. En particular, sugeriría usar NetBeans Profiler o VisualVM (que viene con Java 6, pero veo que estás atascado con Java 5).

Otra cosa que debe probar si conoce el número de objetos de antemano es usar el constructor HashMap (int capacidad, factor de carga doble) en lugar del constructor predeterminado sin argumentos que usa los valores predeterminados de (16,0.75). Si la cantidad de elementos en su HashMap excede (capacidad * factor de carga), la matriz subyacente en el HashMap cambiará de tamaño a la siguiente potencia de 2 y la tabla se volverá a compartir. Esta matriz también requiere un área contigua de memoria, por lo que, por ejemplo, si duplica una matriz de tamaño 32768 a 65536, necesitará una porción de memoria de 256kB libre. Para evitar la asignación extra y rehacer las penalizaciones, solo use una tabla hash más grande desde el principio. También disminuirá la posibilidad de que no tenga un área contigua de memoria lo suficientemente grande como para caber en el mapa.

Las implementaciones generalmente están respaldadas por matrices. Las matrices son bloques de memoria de tamaño fijo. La implementación de hashmap comienza almacenando datos en una de estas matrices a una capacidad dada, digamos 100 objetos.

Si llena la matriz y sigue agregando objetos, el mapa necesita aumentar en secreto su tamaño de matriz. Dado que las matrices son fijas, lo hace creando una matriz completamente nueva, en memoria, junto con la matriz actual, que es un poco más grande. Esto se conoce como crecimiento de la matriz. Luego, todos los elementos de la matriz anterior se copian en la matriz nueva y la matriz anterior se desreferencia con la esperanza de que se recolecte basura y la memoria se libere en algún momento.

Por lo general, el código que aumenta la capacidad del mapa al copiar elementos en una matriz más grande es la causa de tal problema. Hay & Quot; tonto & Quot; implementaciones e inteligentes que utilizan un factor de crecimiento o carga que determina el tamaño de la nueva matriz en función del tamaño de la matriz anterior. Algunas implementaciones ocultan estos parámetros y otras no, por lo que no siempre puede establecerlos. El problema es que cuando no puede configurarlo, elige algún factor de carga predeterminado, como 2. Por lo tanto, la nueva matriz es dos veces el tamaño de la anterior. Ahora su mapa supuestamente de 50k tiene una matriz de respaldo de 100k.

Mire para ver si puede reducir el factor de carga a 0.25 o algo así. esto provoca más colisiones de mapas hash, lo que perjudica el rendimiento pero está llegando a un cuello de botella de memoria y necesita hacerlo.

Usa este constructor:

( http: // java.sun.com/javase/6/docs/api/java/util/HashMap.html#HashMap(int , float))

Probablemente necesite establecer el indicador -Xmx512m o algún número mayor al iniciar Java. Creo que 64mb es el valor predeterminado.

Editado para agregar: Después de calcular cuánta memoria están usando realmente sus objetos con un generador de perfiles, es posible que desee buscar referencias débiles o referencias suaves para asegurarse de que no está reteniendo accidentalmente parte de la memoria como rehén del recolector de basura cuando no más tiempo usándolos.

También es posible que desee echar un vistazo a esto:

http://java.sun.com/docs/hotspot/gc/

Implícito en estas respuestas, Java tiene un tamaño fijo para la memoria y no crece más allá del tamaño de almacenamiento dinámico máximo configurado. Esto es diferente a, digamos, C, donde está limitado solo por la máquina en la que se ejecuta.

De manera predeterminada, la JVM usa un espacio de almacenamiento dinámico limitado. El límite depende de la implementación de JVM, y no está claro qué JVM está utilizando. En sistemas operativos que no sean Windows, un Sun JVM de 32 bits en una máquina con 2 Gb o más utilizará un tamaño de almacenamiento dinámico máximo predeterminado de 1/4 de la memoria física, o 512 Mb en su caso. Sin embargo, el valor predeterminado para un & Quot; cliente & Quot; el modo JVM tiene solo un tamaño de almacenamiento dinámico máximo de 64 Mb, que puede ser con lo que te has encontrado. Las JVM de otros proveedores pueden seleccionar diferentes valores predeterminados.

Por supuesto, puede especificar el límite de almacenamiento dinámico explícitamente con la opción -Xmx<NN>m a java, donde <NN> es el número de megabytes para el almacenamiento dinámico.

Como una suposición aproximada, su tabla hash solo debería estar usando aproximadamente 16 Mb, por lo que debe haber algunos otros objetos grandes en el montón. Si pudiera usar una tecla Comparable en un TreeMap, eso ahorraría algo de memoria.

Ver " Ergonomía en la JVM 5.0 " para más detalles.

El espacio de almacenamiento dinámico de Java está limitado de forma predeterminada, pero eso todavía suena extremo (aunque ¿qué tan grandes son sus 50000 segmentos?)

Sospecho que tiene algún otro problema, como que las matrices en el conjunto crecen demasiado porque todo se asigna en el mismo " slot " (también afecta el rendimiento, por supuesto). Sin embargo, eso parece poco probable si sus puntos están distribuidos uniformemente.

Sin embargo, me pregunto por qué estás usando un HashMap en lugar de un TreeMap. Aunque los puntos son bidimensionales, puede subclasificarlos con una función de comparación y luego realizar búsquedas log (n).

Pensamiento aleatorio: los cubos hash asociados con HashMap no son particularmente eficientes en cuanto a memoria. Es posible que desee probar TreeMap como alternativa y ver si aún proporciona un rendimiento suficiente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top