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