javamemoryout-of-memoryheap-memory

Direct buffer memory


I need to return a rather large file from a web request. The file is around 670mb in size. For the most part this will work fine but after some time the following error will be thrown:

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]

I have set the heap size to 4096mb which I think should be large enough to handle this kinds of files. Furthermore when this error occured I took a heapdump with jmap to analyze the current state. I found two rather large byte[], which should be the file I want to return. But the heap is only around 1.6gb in size and not near the configured 4gb it can be.

According to some other answer (https://stackoverflow.com/a/39984276/5126654) in a similar question I tried running manual gc before returning this file. The problem still occured but now only spardic. The problem occured after some time, but then when I tired running the same request again it seems like the garbage collection took care of whatever caused the problem, but this is not sufficient since the problem apparently still can occur. Is there some other way to avoid this memory problem?


Solution

  • The actual memory buffers managed by DirectByteBuffer are not allocated in the heap. They are allocated using Unsafe.allocateMemory which allocates "native memory". So increasing or decreasing the heap size won't help.

    When the GC detects that a DirectByteBuffer is no longer referenced, a Cleaner is used to free the native memory. However, this happens in the post-collection phase, so if the demand for / turnover of direct buffers is too great, it is possible that the collector won't be able to keep up. If that happens, you get an OOME.


    What can you do about it?

    AFAIK, the only thing you can do is to force more frequent garbage collections. But that can have performance implications. And I don't think it is a guaranteed solution.

    The real solution is to take a different approach.

    The stacktrace shows that you are using Files::readAllBytes to load files into memory. Presumably you are doing this to get the fastest download times that are possible. This is a mistake:

    So, for files of this size, a better idea is to stream the file. Either allocate a large (a few megabytes) ByteBuffer and read / write in a loop, or use a copy the file using Files::copy(...) (javadoc) which should take care of the buffering for you.

    (There is also the option of using something that maps to a Linux sendfile syscall. This copies data from one file descriptor to another without writing it into a user-space buffer.)