我已经为特定的二进制格式(如果有人有兴趣的话,为nfdump)编写了一个解析器类,该类使用java.nio的MappedByteBuffer读取每个数GB的文件。二进制格式只是一系列的 header 和大多数是固定大小的二进制记录,它们通过调用nextRecord()馈送到被调用方,后者将推送状态机,完成后将返回null。表现不错。它可以在开发机器上工作。
在我的生产主机上,它可以运行几分钟或几小时,但始终似乎会抛出“java.lang.InternalError:最近在编译的Java代码中不安全的内存访问操作中发生了错误”,这是Map.getInt中的一个,getShort方法,即 map 中的读取操作。
设置 map 的无争议(?)代码是这样的:
/** 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 之前,我使用各种map.get *方法读取short,int,long和其他字节序列。
我从未见过在我的开发主机上引发异常的情况。但是,我的生产主机与开发之间的重要区别在于,在前者上,我正在NFS上读取这些文件的序列(最终可能会达到6-8TB,并且仍在增长)。在我的开发机上,我在本地选择了较少的这些文件(60GB),但是当它在生产主机上崩溃时,通常要等到60GB的数据。
两台机器都运行Java 1.6.0_20-b02,尽管生产主机正在运行Debian/lenny,而开发主机是Ubuntu/karmic。我不相信这会有所作为。两台机器都有16GB RAM,并以相同的Java堆设置运行。
我认为,如果我的代码中存在错误,那么JVM中就有足够的错误不会抛出适当的异常!但是由于NFS和mmap之间的交互,我认为这只是一个特定的JVM实现错误,可能是6244515的重复出现,已正式修复。
我已经尝试添加“加载”调用以强制MappedByteBuffer将其内容加载到RAM中-这似乎在我完成的一次测试运行中延迟了该错误,但并不能阻止该错误。或者这可能是巧合,这是它坠毁前最长的时间!
如果您已经读了那么多书,并且以前用java.nio做过这种事情,您的直觉是什么?现在我的是不用nio重写它了:)
最佳答案
我将不使用映射的 NIO来重写它。如果您要处理多个文件,则存在映射内存永远不会释放的问题,因此您将用完虚拟内存:注意,这并不一定是与垃圾回收器交互的OutOfMemoryError,这可能是一个问题。无法分配新的映射缓冲区。我会使用FileChannel。
话虽这么说,对NFS文件的大规模操作始终是极为棘手的问题。您最好重新设计系统,以便每个文件都可以由其本地CPU读取。通过这种方式,您还将获得巨大的速度改进,这远远超过了不使用映射缓冲区而导致的20%的损失。