我需要从一个Web请求返回一个相当大的文件。该文件的大小约为670MB。在大多数情况下,这将工作正常,但一段时间后,将抛出以下错误:
java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694) ~[na:1.8.0_162]
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) ~[na:1.8.0_162]
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) ~[na:1.8.0_162]
at sun.nio.ch.Util.getTemporaryDirectBuffer(Util.java:241) ~[na:1.8.0_162]
at sun.nio.ch.IOUtil.read(IOUtil.java:195) ~[na:1.8.0_162]
at sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:159) ~[na:1.8.0_162]
at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:65) ~[na:1.8.0_162]
at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:109) ~[na:1.8.0_162]
at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:103) ~[na:1.8.0_162]
at java.nio.file.Files.read(Files.java:3105) ~[na:1.8.0_162]
at java.nio.file.Files.readAllBytes(Files.java:3158) ~[na:1.8.0_162]
我已经将堆大小设置为4096mb,我认为应该足够大来处理这类文件。此外,当这个错误发生时,我用jmap进行了一次堆转储来分析当前状态。我发现了两个相当大的byte[],这应该是我想要返回的文件。但堆的大小只有1.6gb左右,还没有接近配置的4gb。
根据另一个答案(https://stackoverflow.com/a/39984276/5126654)在一个类似的问题中,我尝试在返回这个文件之前运行手动gc。问题仍然发生,但现在只有spardic。问题发生在一段时间后,但当我厌倦了再次运行相同的请求时,似乎垃圾收集处理了导致问题的任何东西,但这是不够的,因为问题显然仍然可能发生。有没有其他方法来避免这个内存问题?
2条答案
按热度按时间jjjwad0x1#
由
DirectByteBuffer
管理的实际内存缓冲区不在堆中分配,而是使用分配"本机内存"的Unsafe. allocateMemory分配的,因此增加或减少堆大小没有帮助。当GC检测到
DirectByteBuffer
不再被引用时,就会使用Cleaner
来释放本机内存。然而,这发生在收集后阶段,因此如果直接缓冲区的需求/周转量太大,收集器可能无法跟上。如果发生这种情况,就会得到OOME。你能做些什么呢?
AFAIK,你唯一能做的就是强制更频繁的垃圾收集。但是这会影响性能。我不认为这是一个有保证的解决方案。
真正的解决办法是采取不同的方法。
您看到您正在从Web服务器提供大量非常大的文件,stacktrace显示您正在使用
Files::readAllBytes
将它们加载到内存中,然后(大概)使用单个write
发送它们。大概您这样做是为了获得尽可能快的下载时间。这是一个错误:read
系统调用时的延迟。因此,对于这种大小的文件,更好的办法是流式传输文件,要么分配一个大的(几兆字节)
ByteBuffer
并在循环中读/写,* 要么 * 使用Files::copy(...)
(javadoc)复制文件,这样就可以为您提供缓冲。(也可以选择使用Map到Linux
sendfile
syscall的内容,这会将数据从一个文件描述符复制到另一个文件描述符,而不会将其写入用户空间缓冲区。)4ktjp1zp2#
您还可以尝试使用JVM选项
-XX:MaxDirectMemorySize
来增加DirectByteBuffer
所使用的缓冲区大小。Java文档对这个参数并不十分详细。但是根据这个page,除非你指定了-Xmx
标志,否则它默认设置为64MB。所以如果你没有设置这个标志,分配的缓冲区可能太小。或者,如果您有一个非常大的文件并设置了-Xmx
,则派生的2GB可能太小,您仍然可以通过手动设置更大的缓冲区来获益。总而言之,更好的方法可能是像StephenC.