Java NIO MappedByteBuffer OutOfMemoryException

Posted

技术标签:

【中文标题】Java NIO MappedByteBuffer OutOfMemoryException【英文标题】: 【发布时间】:2012-09-21 13:56:09 【问题描述】:

我真的遇到了麻烦:我想使用 FileChannels 和 MappedByteBuffers 读取数 GB 以上的巨大文件 - 我发现的所有文档都表明使用 FileChannel.map() 方法映射文件相当简单。 当然在 2GB 是有限制的,因为所有 Buffer 方法都使用 int 表示位置、限制和容量 - 但是低于该限制的系统隐含限制呢?

实际上,我遇到了很多关于OutOfMemoryExceptions 的问题!而且根本没有真正定义限制的文档! 那么 - 我如何才能将适合 int-limit 的文件安全地映射到一个或多个 MappedByteBuffers 而不会出现异常?

在尝试FileChannel.map() 之前,我可以询问系统我可以安全映射文件的哪个部分吗?如何? 为什么关于这个功能的文档这么少??

【问题讨论】:

【参考方案1】:

我可以提供一些工作代码。这是否解决了你的问题很难说。这会在文件中寻找Hunter 识别的模式。

原创研究见优秀文章Java tip: How to read files quickly(不是我的)。

// 4k buffer size.
static final int SIZE = 4 * 1024;
static byte[] buffer = new byte[SIZE];

// Fastest because a FileInputStream has an associated channel.
private static void ScanDataFile(Hunter p, FileInputStream f) throws FileNotFoundException, IOException 
  // Use a mapped and buffered stream for best speed.
  // See: http://nadeausoftware.com/articles/2008/02/java_tip_how_read_files_quickly
  FileChannel ch = f.getChannel();
  long red = 0L;
  do 
    long read = Math.min(Integer.MAX_VALUE, ch.size() - red);
    MappedByteBuffer mb = ch.map(FileChannel.MapMode.READ_ONLY, red, read);
    int nGet;
    while (mb.hasRemaining() && p.ok()) 
      nGet = Math.min(mb.remaining(), SIZE);
      mb.get(buffer, 0, nGet);
      for (int i = 0; i < nGet && p.ok(); i++) 
        p.check(buffer[i]);
      
    
    red += read;
   while (red < ch.size() && p.ok());
  // Finish off.
  p.close();
  ch.close();
  f.close();

【讨论】:

【参考方案2】:

我使用的是List&lt;ByteBuffer&gt;,其中每个 ByteBuffer 映射到 16 MB 到 1 GB 的块中的文件。我使用 2 的幂来简化逻辑。我用它来映射高达 8 TB 的文件。

内存映射文件的一个关键限制是您受到虚拟内存的限制。如果你有一个 32 位的 JVM,你将无法进行很多映射。

我不会继续为文件创建新的内存映射,因为这些映射永远不会被清除。你可以创建很多,但在某些系统上似乎有大约 32K 的限制(无论它们有多小)

我发现 MemoryMappedFiles 有用的主要原因是它们不需要被刷新(如果您可以假设操作系统不会死机)这允许您以低延迟的方式写入数据,而不必担心丢失太多如果应用程序因不得不 write() 或 flush() 而死机或性能过高,则数据。

【讨论】:

我有created a demo for your idea。谢谢,你的建议真的很有用。我只是不明白关闭缓冲区部分。如果文件在 Windows 中缓冲,您可能不需要关闭文件,并且它可以从缓存而不是硬盘为其他用户提供数据。但是,问题是除非您将其关闭,否则其他用户无法打开该文件。从 scala 构建工具多次重新运行我的程序时,我得到了很多 cannot access the file @ValentinTihomirov 您需要清理内存映射文件的缓冲区,否则文件将在 Windows 上保持锁定状态。 buf = null ; System.gc 你的意思是?【参考方案3】:

您不会使用FileChannel API 一次写入整个文件。相反,您将文件分部分发送。请参阅 Martin Thompson 比较 Java IO 技术性能的帖子中的示例代码:Java Sequential IO Performance

此外,没有太多文档,因为您正在进行依赖于平台的调用。来自map()JavaDoc:

内存映射文件的许多细节本质上是依赖的 在底层操作系统上,因此未指定。

【讨论】:

马丁汤普森很棒。请注意,对于 8GB 文件,FileChannel 实际上没有快得多(!) - 但正如 Martin 所说的“您的里程可能会有所不同。” 当您访问单个单词而不是流时,映射的 IO 要快得多。我已经看到运行that code。内存映射 io 为 50 MB/秒,直接使用 raf 则慢 300 倍。 但是,RAF 对随机访问稍快一些。如果文件只有系统内存的 1/10,那么对于测试的数百万个请求,MM 接近 14 ns/request,而 raf 则慢 1000 倍,即 11 us/req。但是,如果文件大于主内存,则内存映射搜索需要 5 毫秒,而对于超过 20k 的随机访问,RAF 接近 4.8 毫秒。不同之处在于,无论如何,raf 总是访问文件,而 MMF 提供更强大的缓存。两者具有相同的访问时间,但 raf 只读取一个需要的单词,而 MMF 读取整个 4k 页面,因此在长文件中变得稍慢。 抱歉,MMF 以 140 ns 的速度读取 1GB 文件中的随机 long,仅比 RAF 快 100 倍。有趣的是,当您向它请求 readLong 时,RAF 读取单个字节 8 次。【参考方案4】:

文件越大,您就越不希望一次将其全部存储在内存中。设计一种方法来处理文件,一次是一个缓冲区,一次是一行,等等。

MappedByteBuffers 尤其成问题,因为映射内存没有明确的释放,所以一次使用多个基本上肯定会失败。

【讨论】:

我完全走错了方向,想要映射整个文件,你是对的。我误解了将文件映射到内存的概念,认为一切都是虚拟的,操作系统会在需要时将页面加载到内存中......但是对于根本不起作用的千兆或 TB 文件。 @Zordid 它确实会根据需要加载页面,但它会一次映射所有内存,这需要交换空间、内存地址分配……所有宝贵的资源都没有“未定义的释放”时间.

以上是关于Java NIO MappedByteBuffer OutOfMemoryException的主要内容,如果未能解决你的问题,请参考以下文章

Java--Stream,NIO ByteBuffer,NIO MappedByteBuffer性能对比

java大文件读写操作,java nio 之MappedByteBuffer,高效文件/内存映射

Java NIO - MappedByteBuffer 的截断

Java NIO MappedByteBuffer OutOfMemoryException

JavaNIO的深入研究4内存映射文件I/O,大文件读写操作,Java nio之MappedByteBuffer,高效文件/内存映射

如何正确关闭 MappedByteBuffer?