我的任务是在READ&WRITE模式下打开一个大文件,我需要通过搜索起点和终点来搜索该文件中的部分文本.然后我需要将搜索到的文本区域写入新文件并从原始文件中删除该部分.
上面的过程我会做更多次.所以我认为对于这些过程,通过CharBuffer将文件加载到内存中很容易,并且可以通过MATCHER类轻松搜索.但是我在阅读时得到HeapSpace异常,即使我通过执行如下 java -Xms128m -Xmx900m readLargeFile增加到900MB 我的代码是
FileChannel fc = new FileInputStream(fFile).getChannel();
CharBuffer chrBuff = Charset.forName("8859_1").newDecoder().decode(fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()));
Run Code Online (Sandbox Code Playgroud)
对于上面的代码,每个人都建议我将所有内容加载到内存中是一个坏主意,如果文件大小为300 MB则意味着,由于charSet,它将是600MB.
所以上面是我的任务,然后现在建议我一些有效的方法.请注意,我的文件大小会更多,只使用JAVA我会做这些事情.
提前致谢...
您绝对不希望使用Java将300MB文件加载到单个大型缓冲区中.对于大型文件而言,处理事务的方式应该比使用普通I/O更有效,但是当您Matcher对映射到内存中的整个文件进行运行时,您可以非常轻松地耗尽内存.
首先,你的代码存储器将文件映射到内存中...这将在你的虚拟地址空间中消耗300 Meg的内存,因为文件被mmap写入其中,尽管这是在堆外.(请注意,300 Meg的虚拟地址空间被捆绑,直到MappedByteBuffer收集垃圾.请参阅下面的讨论.JavaDoc会map向您发出警告.)接下来,您将创建一个ByteBuffer由此mmaped文件支持的文件.这应该没问题,因为它只是mmaped文件的"视图",因此应该占用最少的额外内存.它将是堆中的一个小对象,带有指向堆外部大对象的"指针".接下来,您将其解码为a CharBuffer,这意味着您复制了300 MB缓冲区,但是您复制了600 MB(在堆上)因为a char是2个字节.
要响应注释,并查看JDK源代码以确定,当您调用map()OP时,实际上将整个文件映射到内存中.查看openJDK 6 b14 Windows本机代码sun.nio.ch.FileChannelImpl.c,首先调用CreateFileMapping,然后调用MapViewOfFile.查看此源代码,如果您要求将整个文件映射到内存中,此方法将完全按照您的要求执行.引用MSDN:
映射文件使得文件的指定部分在调用进程的地址空间中可见.
对于大于地址空间的文件,您一次只能映射一小部分文件数据.第一个视图完成后,您可以取消映射并映射新视图.
OP调用map的方式,文件的"指定部分"是整个文件.这不会导致堆耗尽,但它可能导致虚拟地址空间耗尽,这仍然是OOM错误.这可以像耗尽堆一样彻底地杀死你的应用程序.
最后,当你创建一个时Matcher,Matcher可能会生成这个600 MB的更多副本CharBuffer,具体取决于你如何使用它.哎哟.这是少量对象使用的大量内存!给定一个Matcher,每次打电话toMatchResult(),你都会String复制整个 CharBuffer.此外,每次打电话replaceAll(),你最多都会String复制一份CharBuffer.在最坏的情况下,你将使一个StringBuffer慢慢扩展到结果的完整大小replaceAll(在堆上施加大量内存压力),然后从中创建一个String.
因此,如果你打电话replaceAll给Matcher一个300 MB的文件,并找到你的匹配,那么你将首先制作一系列更大的StringBuffers,直到你得到一个600 MB的文件.然后你会String复制一份StringBuffer.这可以快速轻松地导致堆耗尽.
这是底线: Matchers未针对非常大的缓冲区进行优化.您可以非常轻松地制作许多非常大的物体,而无需计划.我在做类似于你正在做的事情并遇到内存耗尽的时候发现了这个,然后查看源代码Matcher.
注意:没有unmap电话.一旦你打电话map,堆在它外面的堆外的虚拟地址空间MappedByteBuffer就会被卡在那里,直到MappedByteBuffer被垃圾收集.因此,在MappedByteBuffer收集垃圾之前,您将无法对文件执行某些操作(删除,重命名,...).如果在不同的文件上调用map足够的次数,但是堆中没有足够的内存压力来强制进行垃圾回收,那么堆外的内存可能就会内存不足.有关讨论,请参阅错误4724038.
作为上述所有讨论的结果,如果您将使用它来制作Matcher大型文件,并且您将使用replaceAll它Matcher,那么内存映射I/O可能不是可行的方法.它只会在堆上创建太多大对象,并在堆外部占用大量虚拟地址空间.在32位Windows下,对于JVM,您只有2GB(或者如果您更改了设置,3GB)虚拟地址空间,这将在堆内外施加巨大的内存压力.
我为这个答案的长度道歉,但我想要彻底.如果您认为上述任何部分是错误的,请发表评论并说出来.我不会做报复性的downvotes.我非常肯定所有上述内容都是准确的,但如果出现问题,我想知道.
| 归档时间: |
|
| 查看次数: |
2725 次 |
| 最近记录: |