本文介紹了直接緩沖存儲器的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學習吧!
問題描述
我需要從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獲取堆轉儲來分析當前狀態。我發現了兩個相當大的字節[],這應該是我想要返回的文件。但是堆的大小只有1.6 GB左右,還沒有達到配置的4 GB大小。
根據其他一些答案(https://stackoverflow.com/a/39984276/5126654),在一個類似的問題中,我嘗試在返回此文件之前運行手動GC。問題仍然存在,但現在只是零星的。問題在一段時間后出現,但當我再次厭倦運行相同的請求時,似乎垃圾收集已經解決了導致問題的問題,但這是不夠的,因為問題顯然仍然可能發生。是否有其他方法可以避免此內存問題?
推薦答案
DirectByteBuffer
管理的實際內存緩沖區不在堆中分配。它們是使用UnSafe.allocateMemory分配的,它分配本機內存和。因此,增加或減少堆大小無濟于事。
當GC檢測到DirectByteBuffer
不再被引用時,將使用Cleaner
釋放本機內存。但是,這種情況發生在收集后階段,因此如果對直接緩沖區的需求/周轉太大,收集器可能跟不上。如果發生這種情況,您將獲得OOME。
您能對此做些什么?
AFAIK,您唯一可以做的就是強制更頻繁的垃圾回收。但這可能會對性能產生影響。我不認為這是一個有保證的解決方案。
真正的解決方案是采取不同的方法。
您看到您正在從Web服務器提供大量非常大的文件,堆棧跟蹤顯示您正在使用Files::readAllBytes
將它們加載到內存中,然后(假設)使用單個write
發送它們。想必您這樣做是為了盡可能獲得最快的下載時間。這是一個錯誤:
您占用了大量內存(是垃圾收集器的倍數,給垃圾收集器帶來了壓力。這導致了更多的GC運行和偶爾的OOME。它還可能以各種方式影響服務器上的其他應用程序。
傳輸文件的瓶頸可能是而不是從磁盤讀取數據的過程。(真正的瓶頸是通常通過網絡上的TCP流發送數據,或將數據寫入客戶端的文件系統。)
如果您按順序讀取一個大文件,現代Linux操作系統通常會使用預讀大量磁盤塊,并將這些塊保存在(OS)緩沖區緩存中。這將減少您的應用程序進行的read
系統調用的延遲。
因此,對于這種大小的文件,更好的方法是對文件進行流式處理。要么分配一個大的(幾兆字節)ByteBuffer
并循環讀寫,或使用Files::copy(...)
(javadoc)復制文件,這應該會為您負責緩沖。
(也可以選擇使用映射到Linux系統調用的內容。這會將數據從一個文件描述符復制到另一個文件描述符,而不會將其寫入用戶空間緩沖區。)
這篇關于直接緩沖存儲器的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,