2011-05-13 98 views
12

我正在使用內存映射IO作爲索引文件,但問題是如果文件大部分爲空,我無法調整文件大小。截斷內存映射文件

某處前:

MappedByteBuffer map = raf.getChannel().map(MapMode.READ_WRITE, 0, 1 << 30); 
raf.close(); 
// use map 
map.force(); 
map = null; 

調整大小:

for (int c = 0; c < 100; c++) { 
    RandomAccessFile raf = new RandomAccessFile(indexFile, "rw"); 
    try { 
     raf.setLength(newLen); 
     if (c > 0) LOG.warn("used " + c + " iterations to close mapped byte buffer"); 
     return; 
    } catch (Exception e) { 
     System.gc(); 
     Thread.sleep(10); 
     System.runFinalization(); 
     Thread.sleep(10); 
    } finally { 
     raf.close(); 
    } 
} 

當使用Windows或Linux的32位我經常有未作圖問題,但在64位Linux生產環境中的一切似乎工作沒有警告,但文件保持原來的大小。

任何人都可以解釋爲什麼發生這種情況和/或如何解決問題?

+0

我擔心這個問題在某種程度上取決於NFS,緩存或計時,因爲它似乎解決了沒有真正的干預(只是添加日誌記錄和等待,現在它的工作)。即使是在truncationg之後巨大的文件,以及之後未被觸及的文件現在都具有正確的大小。也許在截斷之後記錄新的文件大小會更新一些nfs緩存。 – rurouni 2011-05-13 11:09:32

+0

正在討論的問題類似於[如何取消映射文件](http://stackoverflow.com/questions/2972986),具體請參見[bug#4724038](http://bugs.sun.com/view_bug。怎麼辦?bug_id = 4724038)。 – 2012-05-21 12:50:52

回答

7

你的問題是你正在使用不可靠的方法來關閉映射字節緩衝區(一百個呼叫System.gc()System.runFinalization()不保證你什麼)。不幸的是,在Java的API沒有可靠的方法來做到這一點,但在Sun JVM(也許在某些人太),你可以使用下面的代碼:

public void unmapMmaped(ByteBuffer buffer) { 
    if (buffer instanceof sun.nio.ch.DirectBuffer) { 
    sun.misc.Cleaner cleaner = ((sun.nio.ch.DirectBuffer) buffer).cleaner(); 
    cleaner.clean(); 
    } 
} 

當然是JVM依賴,你應該如果Sun決定以不兼容的方式更改sun.nio.ch.DirectBuffersun.misc.Cleaner(但實際上我不相信會發生這種情況),您隨時可以修復您的代碼。

3

這只是對上一個答案的補充,完全正確。

JDK 1.7對使用sun.misc.Cleaner抱怨說,該名稱空間中的類不是JDK的正式部分,並且可能在將來消失。但是,從1.7開始,它們仍然存在。

如果.clean()方法不可用,則可以使用System.gc()作爲回退方法,但是必須確認這是一種「黑客」,因此必須謹慎使用。

雖然System.gc()不能強制關閉未引用的映射,但實際上它通常會導致清理髮生。在第一次或第二次調用System.gc()期間,32位Linux(和Solaris)上的經驗顯示,在每次測試期間釋放緩衝區。但是,Windows上的行爲是不同的。在大多數情況下,所有映射在第二次調用結束時釋放到System.gc(),但有時需要3次調用。還有一些情況下需要更多的呼叫,並且要求呼叫的次數越來越少。這可能是欺騙性的,因爲測試可能表明4次呼叫都是必需的,只是在一個月之後讓它失敗。然後5個電話可能看起來足夠,只會導致6個月內失敗。

通過使用圍繞FileChannel.truncate()try/catch塊來測試以查看映射是否已發佈,並使用循環在失敗時重新嘗試操作。循環不能是無限的,因爲存在特定堆配置會導致垃圾收集器從不清理映射的病態情況。然而,約10的循環將涵蓋幾乎所有情況。如果這個對象沒有被消失,那麼它就不會去任何地方,應用程序將不得不放棄。這似乎不夠充分,但實際上,這是不太可能的,並且只會成爲JVM中不支持清潔工的問題。