导致VM故障的Java map / nio / NFS问题:“在编译的Java代码中最近的不安全内存访问操作中发生了故障”

我已经为特定的二进制格式编写了一个解析器类( nfdump,如果有人感兴趣的话),它使用java.nio的MappedByteBuffer来读取每个几GB的文件。 二进制格式只是一系列标题和大多数固定大小的二进制记录,它们通过调用nextRecord()输出到被调用者,后者推送状态机,完成时返回null。 它表现很好。 它适用于开发机器。

在我的生产主机上,它可以运行几分钟或几小时,但似乎总是抛出“java.lang.InternalError:在编译的Java代码中最近的一个不安全的内存访问操作中发生了一个错误”,指法其中一个Map.getInt ,getShort方法,即地图中的读取操作。

设置地图的无争议(?)代码是这样的:

/** 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); } 

然后我使用各种map.get *方法读取short,int,long和其他字节序列,然后点击文件末尾并关闭地图。

我从来没有见过我的开发主机上抛出的exception。 但是我的生产主机和开发之间的重要区别在于前者,我正在通过NFS读取这些文件的序列(最终可能是6-8TB,仍然在增长)。 在我的开发机器上,我在本地选择了较少的这些文件(60GB),但是当它在生产主机上爆炸时,它通常会在它达到60GB的数据之前。

两台机器都在运行java 1.6.0_20-b02,虽然生产主机运行的是Debian / lenny,但开发主机是Ubuntu / karmic。 我不相信会有任何不同。 两台机器都有16GB RAM,并且使用相同的Java堆设置运行。

我认为如果我的代码中存在错误,那么JVM中就有足够的错误不会给我一个正确的exception! 但我认为由于NFS和mmap之间的交互,这只是一个特定的JVM实现错误,可能是正式修复的6244515的重现。

我已经尝试添加一个“加载”调用来强制MappedByteBuffer将其内容加载到RAM中 – 这似乎延迟了我已完成的一次测试运行中的错误,但并未阻止它。 或者它可能是巧合,它是崩溃前最长的!

如果你已经读过这篇文章并且之前用java.nio做过这种事情,你的直觉是什么? 现在我的是在没有nio的情况下重写它:)

我会在不使用映射的 NIO的情况下重写它。 如果您正在处理多个文件,则存在一个问题,即映射内存永远不会被释放,因此您将耗尽虚拟内存:NB这不一定只是与垃圾收集器交互的OutOfMemoryError,它将是一个无法分配新的映射缓冲区。 我会使用FileChannel。

话虽如此,对NFS文件的大规模操作总是非常有问题。 重新设计系统会更好,这样每个文件都可以由本地CPU读取。 您也可以通过这种方式获得巨大的速度提升,远远超过不使用映射缓冲区将损失的20%。