2010-06-01 6 views
9

He escrito una clase analizadora para un formato binario particular (nfdump si alguien está interesado) que utiliza MappedByteBuffer de java.nio para leer archivos de unos pocos GB cada uno. El formato binario es simplemente una serie de encabezados y, en su mayoría, registros binarios de tamaño fijo, que se envían a la llamada llamando a nextRecord(), que empuja a la máquina de estado y devuelve nulo cuando termina. Funciona bien. Funciona en una máquina de desarrollo.Problema Java map/nio/NFS que causa un error VM: "se produjo un error en una operación reciente de acceso a memoria inseguro en código Java compilado"

En mi host de producción, puede ejecutarse durante unos minutos u horas, pero siempre parece lanzar "java.lang.InternalError: se produjo un error en una operación reciente de acceso a memoria inseguro en el código compilado de Java", digitación uno de los métodos Map.getInt, getShort, es decir, una operación de lectura en el mapa. (?)

El controvertido código que configura el mapa es la siguiente:

/** Set up the map from the given filename and position */ 
    protected void open() throws IOException { 
      // Set up buffer, is this all the flexibility we'll need? 
      channel = new FileInputStream(file).getChannel();  
      MappedByteBuffer map1 = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 
      map1.load(); // we want the whole thing, plus seems to reduce frequency of crashes? 
      map = map1; 
      // assumes the host writing the files is little-endian (x86), ought to be configurable 
      map.order(java.nio.ByteOrder.LITTLE_ENDIAN); 
      map.position(position); 
    } 

y luego utilizar los distintos métodos map.get * para leer pantalones cortos, largos y enteros, otras secuencias de bytes, antes golpeando el final del archivo y cerrando el mapa.

Nunca he visto la excepción lanzada en mi host de desarrollo. Pero el punto significativo de diferencia entre mi host de producción y el desarrollo es que en el primero, estoy leyendo secuencias de estos archivos a través de NFS (probablemente 6-8TB eventualmente, aún creciendo). En mi máquina de desarrollo, tengo una selección más pequeña de estos archivos localmente (60GB), pero cuando explota en el host de producción, generalmente es mucho antes de que llegue a los 60GB de datos.

Ambas máquinas ejecutan java 1.6.0_20-b02, aunque el host de producción ejecuta Debian/lenny, el host dev es Ubuntu/karmic. No estoy convencido de que eso haga la diferencia. Ambas máquinas tienen 16 GB de RAM y funcionan con la misma configuración de almacenamiento en java.

Considero que si hay un error en mi código, ¡hay suficiente error en la JVM para no arrojarme una excepción! Pero creo que es solo un error de implementación de JVM en particular debido a las interacciones entre NFS y mmap, posiblemente una recurrencia de 6244515 que se ha corregido oficialmente.

Ya intenté agregar una llamada de "carga" para forzar a MappedByteBuffer a cargar sus contenidos en la RAM, esto pareció retrasar el error en la única ejecución de prueba que hice, pero no la evité. ¡O podría ser una coincidencia que fue el tiempo más largo que había pasado antes de estrellarse!

Si ha leído hasta aquí y ha hecho este tipo de cosas con java.nio antes, ¿cuál sería su instinto? En este momento el mío es reescribirlo sin nio :)

+0

Supongo que ya has visto D8 de (http://nfs.sourceforge.net/) – Justin

+0

No tuve, gracias, pero tampoco estoy escribiendo en estos archivos. –

+0

Estoy viendo esto ocurrir con los archivos mapeados de memoria en los sistemas locales de archivos ext4 y tmpfs con Java 7u1. –

Respuesta

4

Lo reescribiría sin usar mapeado NIO. Si está tratando con más de un archivo, existe el problema de que la memoria mapeada nunca se libera, por lo que se quedará sin memoria virtual: NB esto no es necesariamente solo un OutOfMemoryError que interactúa con el recolector de basura, sería un falla al asignar el nuevo buffer mapeado. Yo usaría un FileChannel.

Dicho esto, las operaciones a gran escala en archivos NFS siempre son extremadamente problemáticas. Sería mucho mejor rediseñar el sistema para que cada archivo sea leído por su CPU local. También obtendrás mejoras de velocidad enormes de esta manera, mucho más del 20% que perderás al no usar los buffers mapeados.

+0

Pensé que faltaba el espacio de direcciones virtuales, pero, como dijiste, debería manifestarse en una falla de asignación (además, estoy leyendo solo un archivo a la vez, y en un sistema de 64 bits). Probablemente reorganice los servidores para que los archivos vivan en el mismo servidor que el proceso de Java, y evite cualquier problema con NFS. En el corto plazo lo leeré todo en un ByteBuffer, pero debido a que varios hilos están leyendo los mismos archivos, a menudo al mismo tiempo, ¡está reimplementando cosas a las que mmap * ought * debería ser una solución elegante! –

+0

Sí, esperaba una respuesta que me permitiera mantener mmap, solo necesitaba un empujón para que alguien dijera "no va a funcionar" :) El código de apertura() ahora simplemente lee todo en un asignado ByteBuffer. Si bien mi instinto era preocuparme por el desperdicio de memoria (como varios lectores = varias copias en el montón), no he visto una caída en el rendimiento en comparación con las corridas anteriores, por lo que realmente no me puedo quejar. He dejado el código anterior comentado con la esperanza de que pueda restaurar el mmap "elegante", pero suponiendo que mis archivos nfdump se mantengan del mismo tamaño, probablemente no lo vuelva a necesitar. –

+0

'varios lectores = varias copias en el montón': solo si haces esas varias copias. ¿No puedes organizar algún tipo de acceso único? – EJP

Cuestiones relacionadas