簡體   English   中英

在內存有限的系統上寫入大文件時,如何避免mapFailed()錯誤

[英]How do I avoid mapFailed() error when writing to large file on system with limited memory

我剛剛在我的opensrc庫代碼中遇到錯誤,該代碼分配了一個較大的緩沖區以對大型flac文件進行修改,該錯誤僅發生在使用Java 1.8.0_74 25.74-b02 32位且內存為3Gb的舊PC機上

最初我只是分配一個緩沖區

ByteBuffer audioData = ByteBuffer.allocateDirect((int)(fc.size() - fc.position()));

但是一段時間以來

MappedByteBuffer mappedFile = fc.map(MapMode.READ_WRITE, 0, totalTargetSize);

我的(錯誤)理解是,映射緩沖區使用的內存比直接緩沖區使用的內存少,因為整個映射緩沖區不必同時在內存中,而只在使用一部分。 但是這個答案說使用映射的字節緩沖區是一個壞主意,所以我不知道如何工作

Java大文件上傳引發java.io.IOException:映射失敗

完整的代碼可以在這里看到

盡管映射的緩沖區在任何時間點可能使用較少的物理內存,但它仍然需要等於緩沖區的總(邏輯)大小的可用(邏輯)地址空間。 使情況更糟的是,它可能(可能)要求地址空間是連續的。 無論出於何種原因,那台舊計算機似乎都無法提供足夠的額外邏輯地址空間。 兩種可能的解釋是(1)有限的邏輯地址空間+大量的緩沖內存要求,以及(2)OS對可映射為I / O文件的內存量施加了一些內部限制。

關於第一種可能性,請考慮以下事實:在虛擬內存系統中,每個進程都在其自己的邏輯地址空間中執行(因此可以訪問整個2 ^ 32字節的地址)。 因此,如果在嘗試實例化MappedByteBuffer時間點上,JVM進程的當前大小加上MappedByteBuffer的總(邏輯)大小大於2 ^ 32字節( MappedByteBuffer GB),則您將遇到OutOfMemoryError (或類選擇替代的任何錯誤/異常,例如IOException: Map failed )。

關於第二種可能性,可能最簡單的評估方法是在嘗試實例化MappedByteBuffer對程序/ JVM進行概要分析。 如果JVM進程分配的內存+所需的totalTargetSize遠低於2 ^ 32字節的上限,但是您仍然收到“映射失敗”錯誤,則可能是內部操作系統對內存映射文件的大小有一些限制根本原因。

那么,這意味着什么可能的解決方案呢?

  1. 只是不要使用那台舊PC。 (首選,但可能不可行)
  2. 確保JVM中的其他所有內容在MappedByteBuffer的生命周期內具有盡可能低的內存占用。 (合理,但可能無關緊要,而且絕對不切實際)
  3. 將該文件分成較小的塊,然后一次僅處理一個塊。 (可能取決於文件的性質)
  4. 使用其他/較小的緩沖區。 ...並且忍受性能下降。 (即使最令人沮喪,這也是最現實的解決方案)

另外,針對您的問題案例的totalTargetSize到底是什么?


編輯:

經過一些挖掘后,很明顯IOException是由於32位環境中的地址空間用盡了 當文件本身是根據2 ^ 32字節或者由於缺乏足夠的連續的地址空間,或由於在同一時間合並在JVM其它足夠大的地址空間的要求這甚至可以發生MappedByteBuffer請求( 見注釋 )。 需要說明的是, 即使最初的原因是ENOMEM ,仍然可以拋出IOE而不是OOM。 而且,特別是在較舊的[在此處插入Microsoft OS] 32位環境中似乎存在問題( 例如example )。

因此,看來您有三個主要選擇。

  1. 完全使用“ 64位JRE或...另一個操作系統 ”。
  2. 使用其他類型的較小緩沖區,並分塊處理文件。 (並且由於不使用映射緩沖區而導致性能下降)
  3. 出於性能原因,繼續使用MappedFileBuffer ,但也要對文件進行較小的操作以解決地址空間限制。

MappedFileBuffer在較小的塊中使用MappedFileBuffer作為第三,是因為在取消MappedFileBuffer存在的既定問題和未解決的問題( 示例 ),這是您在處理每個塊之間必須要做的,以避免碰到32位上限,這是由於累積映射的組合地址空間占用空間所致。 (注意:僅在32位地址空間上限而不是某些內部操作系統限制是問題的情況下才適用...如果是后者,則忽略此段。)您可以嘗試此策略 (刪除所有引用,然后運行GC),但實際上,您將受制於GC和您的底層操作系統如何在內存映射文件上進行交互。 嘗試直接或多或少地直接操縱底層內存映射文件的其他可能的解決方法( 示例 )極其危險,並且受到Oracle的特別譴責( 請參閱最后一段 )。 最后,考慮到GC行為仍然是不可靠的,此外,官方文檔明確指出“ 未指定內存映射文件的許多詳細信息 ”,因此我建議您使用MappedFileBuffer這樣的方法,無論您可能會遇到任何變通辦法關於。

因此,除非您願意冒險,否則我建議要么遵循Oracle的明確建議(第1點),要么使用不同的緩沖區類型將文件作為一系列較小的塊處理(第2點)。

當您分配緩沖區時,您基本上會從操作系統中獲得虛擬內存塊(並且此虛擬內存是有限的,理論上最大的是您的RAM +配置的任何交換-其他任何其他程序和OS首先搶占的東西)

內存映射只會將磁盤文件上占用的空間添加到虛擬內存中(好吧,有一些開銷,但不是很多)-因此您可以獲得更多的空間。

這些都不是必須一直存在於RAM中,它的一部分可以在任何給定時間被換出到磁盤中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM