gpt4 book ai didi

java - 直接缓冲存储器

转载 作者:行者123 更新时间:2023-12-03 15:48:48 24 4
gpt4 key购买 nike

我需要从 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。问题仍然存在,但现在只是偶尔出现。一段时间后问题出现了,但是当我再次运行相同的请求时,似乎垃圾收集处理了导致问题的任何问题,但这还不够,因为问题显然仍然可能发生。有没有其他方法可以避免这个内存问题?

最佳答案

DirectByteBuffer 管理的实际内存缓冲区不在堆中分配。它们是使用 Unsafe.allocateMemory 分配的,它分配“ native 内存”。因此,增加或减少堆大小无济于事。
当 GC 检测到 DirectByteBuffer不再被引用,一个 Cleaner用于释放 native 内存。但是,这种情况发生在收集后阶段,因此如果直接缓冲区的需求/周转量太大,则收集器可能会跟不上。如果发生这种情况,您会得到一个 OOME。

你能为这个做什么?
AFAIK,你唯一能做的就是强制更频繁的垃圾收集。但这可能会对性能产生影响。而且我认为这不是一个有保证的解决方案。
真正的解决方案是采取不同的方法。
您看到您正在从网络服务器提供大量非常大的文件,并且堆栈跟踪显示您正在使用 Files::readAllBytes将它们加载到内存中,然后(大概)使用单个 write 发送它们.据推测,您这样做是为了获得可能的最快下载时间。这是个错误:

  • 您正在占用大量内存(垃圾收集器的倍数和压力。这会导致更多的 GC 运行和偶尔的 OOME。它还可能以各种方式影响您服务器上的其他应用程序。
  • 传输文件的瓶颈可能不是从磁盘读取数据的过程。 (真正的瓶颈通常是通过网络上的 TCP 流发送数据,或将其写入客户端的文件系统。)
  • 如果您按顺序读取大文件,现代 Linux 操作系统通常会使用预读多个磁盘块并将这些块保存在 (OS) 缓冲区缓存中。这将减少 read 上的延迟您的应用程序进行的系统调用。

  • 因此,对于这种大小的文件,更好的主意是流式传输文件。要么分配一个大的(几兆字节) ByteBuffer并循环读/写,或使用 Files::copy(...) 复制文件( javadoc ) 应该为您处理缓冲。
    (也可以选择使用映射到 Linux sendfile 系统调用的东西。这会将数据从一个文件描述符复制到另一个文件描述符,而无需将其写入用户空间缓冲区。)

    关于java - 直接缓冲存储器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59631145/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com