2010-01-14 12 views
13

Estoy desarrollando una aplicación en Java que se ejecuta en dispositivos con Windows Mobile. Para lograr esto, hemos utilizado el Esmertec JBed JVM, que no es perfecto, pero por el momento estamos atascados. Recientemente hemos recibido quejas de clientes sobre OutOfMemoryErrors. Después de jugar un montón de cosas, descubrí que el dispositivo tiene mucha memoria libre (aproximadamente 4 MB).Evite la fragmentación de la memoria al asignar muchas matrices en Java

Los OutOfMemoryErrors siempre se producen en el mismo punto del código y es cuando se expande un StringBuffer para agregarle algunos caracteres. Después de agregar algo de registro en esta área, encontré que mi StringBuffer tenía aproximadamente 290000 caracteres con una capacidad de aproximadamente 290500. La estrategia de expansión de la matriz de caracteres internos es simplemente duplicar el tamaño, por lo que estaría intentando asignar una matriz de alrededor de 580000 caracteres. Imprimí el uso de la memoria también esta vez y encontré que estaba usando aproximadamente 3.8MB de aproximadamente 6.8MB en total (aunque he visto que la memoria total disponible aumenta a alrededor de 12MB a veces, por lo que hay mucho espacio para la expansión). Entonces es en este punto que la aplicación informa un OutOfMemoryError, que no tiene mucho sentido dado cuánto todavía hay libre.

Empecé a pensar en el funcionamiento de la aplicación hasta este punto. Básicamente lo que está sucediendo es que estoy analizando un archivo XML usando MinML (un pequeño analizador de Saxo XML). Uno de los campos en el XML tiene aproximadamente 300k caracteres. El analizador transmite los datos del disco y, de forma predeterminada, solo carga 256 caracteres a la vez. Entonces, cuando llegue al campo en cuestión, el analizador llamará al método 'caracteres()' del manejador más de 1000 veces. Cada vez creará un nuevo carácter [] con 256 caracteres. El controlador simplemente agrega estos caracteres a un StringBuffer. El tamaño inicial predeterminado de StringBuffer es solo 12, de modo que cuando los caracteres se añaden al búfer, tendrá que crecer varias veces (cada vez creando un nuevo carácter []).

Mi suposición a partir de esto fue que, si bien hay suficiente memoria libre ya que los anteriores [] pueden ser basura, tal vez no haya un bloque de memoria contiguo lo suficientemente grande como para adaptarse a la nueva matriz que estoy tratando de asignar. Y tal vez la JVM no es lo suficientemente inteligente como para expandir el tamaño del montón porque es estúpido y piensa que no es necesario porque aparentemente hay suficiente memoria libre.

Así que mi pregunta es: ¿Alguien tiene alguna experiencia con esta JVM y podría confirmar o desmentir de manera concluyente mis suposiciones acerca de la asignación de memoria? Y también, ¿alguien tiene alguna idea (suponiendo que mis suposiciones sean correctas) sobre cómo mejorar la asignación de las matrices para que la memoria no se fragmente?

Nota: Las cosas que he intentado ya:

  • que aumentó el tamaño de la matriz inicial de la StringBuffer y yo increaed el tamaño de lectura del analizador de manera que no tenga que crear tantas matrices.
  • Cambié la estrategia de expansión de StringBuffer de modo que una vez que alcanzara un cierto umbral de tamaño, solo se expandiría en un 25% en lugar de un 100%.

Hacer ambas cosas ayudó un poco, pero a medida que aumente el tamaño de los datos xml, todavía obtengo OutOfMemoryErrors en un tamaño bastante bajo (aproximadamente 350kb).

Otra cosa que se debe agregar: todas estas pruebas se realizaron en un dispositivo utilizando la JVM en cuestión. Si ejecuto el mismo código en el escritorio usando Java SE 1.2 JVM, no tengo ningún problema, o al menos no entiendo el problema hasta que mis datos alcancen los 4MB de tamaño.

EDIT:

otra cosa He intentado lo que ha ayudado es un poco me puse la XMS a 10M. Así que esto supera el problema de que la JVM no expande el montón cuando debería y me permite procesar más datos antes de que ocurra el error.

Respuesta

2

Solo por el hecho de actualizar mi propia pregunta, he encontrado que la mejor solución era establecer el tamaño mínimo de almacenamiento dinámico (lo configuré a 10M).Esto significa que la JVM nunca tiene que decidir si expandir o no el montón y, por lo tanto, nunca (hasta ahora en prueba) muere con un OutOfMemoryError aunque debería tener mucho espacio. Hasta ahora en la prueba, hemos podido triplicar la cantidad de datos que analizamos sin error y probablemente podríamos ir más allá si realmente lo necesitáramos.

Esto es un truco para una solución rápida para mantener contentos a los clientes existentes, pero ahora estamos buscando una JVM diferente y le informaré con una actualización si esa JVM maneja mejor este scneario.

0

Creo que tiene mucha memoria, pero está creando una gran cantidad de objetos de referencia. Pruebe este artículo: https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-1049545.html?tag=rbxccnbtr1 para obtener más información.

+0

¿Estás seguro? Ese artículo habla sobre cómo hacer que los objetos * sean más fáciles * para recolectar basura. –

+0

No estoy creando ningún objeto de referencia? Como dije, no creo que tenga un problema con los objetos que no se recogen porque la JVM informa suficiente memoria libre. Es una cuestión de dónde está la memoria libre? ¿Está fragmentado? ¿Es por eso que la JVM no puede asignar mi nueva matriz? – DaveJohnston

0

No estoy seguro de si estos StringBuffers están siendo asignados dentro de MinML. Si es así, ¿supongo que tiene la fuente para ello? Si lo hace, entonces tal vez mientras escanea una cadena, si la cadena alcanza una cierta longitud (digamos, 10000 bytes), podría mirar hacia delante para determinar la longitud exacta de la cadena, y reasignar una memoria intermedia a ese tamaño . Esto es feo, pero salvaría la memoria. (Puede ser incluso más rápido que el no hacer las búsquedas hacia delante, ya que estás pueden salvar muchas reasignaciones.)

Si hacer no tiene acceso a la fuente MinML, entonces no estoy seguro de lo la duración de StringBuffer es relativa al documento XML. Pero esta sugerencia (aunque es incluso más fea que la anterior) podría funcionar: dado que obtiene el XML del disco, quizás podría analizarlo previamente usando (por ejemplo) un analizador SAX, únicamente para obtener el tamaño de la cadena. campos, y asignar los StingBuffers en consecuencia?

+0

StringBuffers se asignan en los objetos Handler para SaxParser (que en este caso es MinML). Entonces el manejador en cuestión asigna un StringBuffer y luego cada vez que se llama al método characters() se añaden más datos. No estoy escaneando una cadena, todo se transmite desde el archivo, por lo que no puedo averiguar el tamaño de la cadena final por adelantado a menos que haga lo que dijo en su segunda sugerencia y analice el archivo dos veces. Pero como dijiste, es feo y lleva mucho tiempo. – DaveJohnston

+0

Feo, sí. Pero puede ser más rápido de lo que cabría esperar, especialmente si su método actual requiere muchas reasignaciones. –

0

¿Puede obtener un volcado de pila del dispositivo?

Si obtiene el volcado del montón y está en un formato compatible, algunos analizadores de memoria Java brindan información sobre el tamaño de los bloques de memoria contigua. Me acuerdo de ver esta funcionalidad en el IBM Montón Analizador http://www.alphaworks.ibm.com/tech/heapanalyzer, sino también comprobar el más hasta la fecha Eclipse Memory Analyzer http://www.eclipse.org/mat/

Si usted tiene la posibilidad de modificar el archivo XML, que probablemente la forma más rápida a cabo. El análisis XML en Java siempre requiere bastante memoria y 300K es bastante para un solo campo. En su lugar, podría intentar separar este campo en un archivo distinto de xml.

+0

Dudo mucho que pueda obtener un volcado dinámico, la JVM es muy limitada en lo que puede hacer con ella, o al menos no está bien documentada, por lo que no sabría cómo hacerlo. Modificar el XML es una posibilidad que estamos considerando como último recurso porque el XML es un conjunto de resultados de búsqueda devueltos por un servidor. Cambiarlo significaría hacer cambios en la estructura de nuestro servidor para evitar lo que parece ser un problema con la JVM. Si eso es lo que ocurre, esperamos encontrar una forma de que la JVM funcione correctamente. – DaveJohnston

1

De lo que sé sobre JVMs, la fragmentación nunca debería ser un problema que tiene para resolver. Si no hay más espacio para la asignación, ya sea debido a la fragmentación o no, el recolector de basura debería ejecutarse, y los GC también suelen comprimir los datos para resolver problemas de fragmentación.

Destacar - que sólo recibe "sin memoria" errores después la GC se corrió y todavía no hay suficiente memoria podrían ser liberados.

En su lugar, trataría de profundizar en las opciones de la JVM específica que está ejecutando. Por ejemplo, un recolector de basura "copiado" usa solo la mitad de la memoria disponible a la vez, por lo que cambiar su máquina virtual para usar otra cosa podría liberar la mitad de su memoria.

Realmente no estoy sugiriendo que su máquina virtual use GC de copia simple, solo sugiero probar esto en el nivel de máquina virtual.

+0

El soporte para la JVM que estoy usando es prácticamente inexistente (a menos que alguien sepa de dónde obtener soporte para Esmertec JBed CDC ??). ¿Alguna idea de cuáles son las opciones de línea de comando estándar para cambiar las opciones de GC? – DaveJohnston

+0

@DaveJohnston: puede consultar la documentación de JVM populares y esperar que las suyas se comporten de la misma manera; pero no hay un estándar definido por las especificaciones Java VM (de hecho, dice explícitamente: "el diseño de la memoria de las áreas de datos en tiempo de ejecución, el algoritmo de recolección de basura utilizado [...] queda a discreción del implementador")) – Oak

2

Quizás podría probar VTD luz. Parece más eficiente con la memoria que SAX. (Sé que es un gran cambio.)

Cuestiones relacionadas