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

Posted

技术标签:

【中文标题】Java map / nio / NFS 问题导致 VM 故障:“在最近编译的 Java 代码中的不安全内存访问操作中发生故障”【英文标题】:Java map / nio / NFS issue causing a VM fault: "a fault occurred in a recent unsafe memory access operation in compiled Java code" 【发布时间】:2010-06-01 11:36:14 【问题描述】:

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

在我的生产主机上,它可以运行几分钟或几小时,但似乎总是抛出“java.lang.InternalError: a fault occurred in a recent unsafe memory access operation incompiled Java code”,指法其中一个Map.getInt、getShort方法,即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.get* 方法来读取 short、int、long 和其他字节序列,然后点击文件末尾并关闭地图。

我从未见过在我的开发主机上引发的异常。但我的生产主机和开发主机之间的显着区别在于,在前者上,我通过 NFS 读取这些文件的序列(最终可能 6-8TB,仍在增长)。在我的开发机器上,我在本地选择的这些文件较少(60GB),但是当它在生产主机上崩溃时,通常早在获得 60GB 数据之前。

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

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

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

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

【问题讨论】:

我猜你已经看过 (nfs.sourceforge.net) 的 D8 我没有,谢谢,但我也没有写入这些文件。 我看到这种情况发生在本地 ext4 和 tmpfs 文件系统上的内存映射文件和 Java 7u1 上。 ...它发生在我用完物理内存时(在顶部查看),即使我有 10 GB 的空闲交换空间。 【参考方案1】:

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

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

【讨论】:

我确实想过缺少虚拟地址空间,但就像你说的那样,这应该表现为映射失败(另外我一次只读取一个文件,并且在 64 位系统上)。我可能会重新排列服务器,以便文件与 java 进程位于同一服务器上,并避免出现任何 NFS 问题。在短期内,我会将它全部读入一个 ByteBuffer,但由于多个线程经常同时读取相同的文件,它正在重新实现 mmap 应该 是一个优雅的解决方案的东西! 是的,我希望得到一个让我保留 mmap 的答案,我只需要推动其他人说“它不会工作”:) open() 代码现在只是读取全部放入分配的 ByteBuffer 中。虽然我的直觉是担心内存浪费(因为几个读者 = 堆上的几个副本),但与以前的运行相比,我没有看到性能下降,所以不能抱怨。我留下了旧代码的注释,希望我可以恢复“优雅”的 mmap,但假设我的 nfdump 文件保持相同的大小,我可能不再需要它了。 '几个读者 = 堆上的几个副本':只有当你制作了这几个副本时。你不能组织某种单例访问吗?

以上是关于Java map / nio / NFS 问题导致 VM 故障:“在最近编译的 Java 代码中的不安全内存访问操作中发生故障”的主要内容,如果未能解决你的问题,请参考以下文章

Solr 解决 NFS 问题

Java NIO为何导致堆外内存OOM了?

内存映射文件java NIO

使用 java.nio.MappedByteBuffer 时防止 OutOfMemory

java nio消息半包粘包解决方案

java io以及nio的理解