使用字节缓冲区和NIO时如何避免OutOfMemoryError?
我正在使用ByteBuffers和FileChannels将二进制数据写入文件。当对大文件或者连续对多个文件执行此操作时,出现OutOfMemoryError异常。
我在其他地方读过,将" Bytebuffers"与NIO一起使用已损坏,应避免使用。你们中的任何人是否已经面临过此类问题,并找到了一种解决方案,可以在Java中的文件中高效保存大量二进制数据?
jvm选项-XX:MaxDirectMemorySize
是否可行?
解决方案
回答
我会说不要创建一个包含所有数据的巨大ByteBuffer。创建一个小得多的ByteBuffer,将其填充数据,然后将此数据写入FileChannel。然后重置ByteBuffer并继续直到写入所有数据。
回答
如果我们以随机方式访问文件(在此处阅读,跳过,在此处写入,向后移动),则我们有问题;-)
但是,如果仅编写大文件,则应认真考虑使用流。 java.io.FileOutputStream可以直接用于逐字节写入文件,也可以包装在任何其他流(即DataOutputStream,ObjectOutputStream)中,以方便编写浮点数,整数,字符串甚至可序列化的对象。存在用于读取文件的类似类。
流为我们提供了在(几乎)任意小内存中处理任意大文件的便利。在绝大多数情况下,它们是访问文件系统的首选方式。
回答
查看Java的"映射的字节缓冲区",也称为"直接缓冲区"。基本上,此机制使用操作系统的虚拟内存分页系统将缓冲区直接"映射"到磁盘。操作系统将非常快速地自动神奇地自动将字节移入磁盘和内存,并且我们不必担心更改虚拟机选项。与传统的基于Java流的I / O相比,这还将使我们能够利用NIO的改进性能,而不会产生任何奇怪的骇客。
我能想到的仅有两个要点是:
- 在32位系统上,所有映射的字节缓冲区的总大小限制为不足4GB。 (这实际上是对我的应用程序的限制,并且我现在在64位体系结构上运行。)
- 实现是特定于JVM的,不是必需的。我使用Sun的JVM,没有问题,但是使用YMMV。
Kirk Pepperdine(一个有点出名的Java性能专家)参与了一个网站www.JavaPerformanceTuning.com,该网站提供了更多MBB详细信息:NIO Performance Tips
回答
前两个响应似乎很合理。至于命令行开关是否可以工作,取决于内存使用量达到极限的速度。如果我们没有足够的内存和虚拟内存来至少使可用内存增加三倍,那么我们将需要使用给出的替代建议之一。
回答
假设我们以增量方式而不是一次全部写入通道,如先前的答案也指出的那样,使用transferFrom方法将对此有所帮助。
回答
这可能取决于特定的JDK供应商和版本。
在某些Sun JVM中,GC中存在一个错误。直接内存不足不会在主堆中触发GC,但是直接内存由主堆中的垃圾直接ByteBuffer固定。如果主堆大部分是空的,那么很长一段时间都不会收集它们。
即使我们自己不是直接使用直接缓冲区,这也会使我们感到厌倦,因为JVM可能会代表我们创建直接缓冲区。例如,将非直接ByteBuffer写入SocketChannel会在幕后创建一个直接缓冲区,以用于实际的I / O操作。
解决方法是自己使用少量直接缓冲区,并保留它们以备重用。