2009-12-18 61 views
2

我有一個非常大的雙打數組,我使用基於磁盤的文件和MappedByteBuffers的分頁列表來處理,請參閱this question瞭解更多背景信息。我在使用Java 1.5的Windows XP上運行。爲什麼使用Java MappedByteBuffers獲得「沒有足夠的存儲空間來處理此命令」?

這裏是我的代碼,不會對文件緩衝區分配的重要組成部分......

try 
{ 
// create a random access file and size it so it can hold all our data = the extent x the size of a double 
f = new File(_base_filename); 
_filename = f.getAbsolutePath(); 
_ioFile = new RandomAccessFile(f, "rw"); 
_ioFile.setLength(_extent * BLOCK_SIZE); 
    _ioChannel = _ioFile.getChannel(); 

    // make enough MappedByteBuffers to handle the whole lot 
_pagesize = bytes_extent; 
long pages = 1; 
long diff = 0; 
while (_pagesize > MAX_PAGE_SIZE) 
{ 
    _pagesize /= PAGE_DIVISION; 
    pages *= PAGE_DIVISION; 

    // make sure we are at double boundaries. We cannot have a double spanning pages 
    diff = _pagesize % BLOCK_SIZE; 
    if (diff != 0) _pagesize -= diff; 

} 

// what is the difference between the total bytes associated with all the pages and the 
// total overall bytes? There is a good chance we'll have a few left over because of the 
// rounding down that happens when the page size is halved 
diff = bytes_extent - (_pagesize * pages); 
if (diff > 0) 
{ 
    // check whether adding on the remainder to the last page will tip it over the max size 
    // if not then we just need to allocate the remainder to the final page 
    if (_pagesize + diff > MAX_PAGE_SIZE) 
    { 
    // need one more page 
    pages++; 
    } 
} 

// make the byte buffers and put them on the list 
int size = (int) _pagesize ; // safe cast because of the loop which drops maxsize below Integer.MAX_INT 
int offset = 0; 
for (int page = 0; page < pages; page++) 
{ 
    offset = (int) (page * _pagesize); 

    // the last page should be just big enough to accommodate any left over odd bytes 
    if ((bytes_extent - offset) < _pagesize) 
    { 
    size = (int) (bytes_extent - offset); 
    } 

    // map the buffer to the right place 
    MappedByteBuffer buf = _ioChannel.map(FileChannel.MapMode.READ_WRITE, offset, size); 

    // stick the buffer on the list 
    _bufs.add(buf); 
} 

Controller.g_Logger.info("Created memory map file :" + _filename); 
Controller.g_Logger.info("Using " + _bufs.size() + " MappedByteBuffers"); 
    _ioChannel.close(); 
    _ioFile.close(); 
} 
catch (Exception e) 
{ 
Controller.g_Logger.error("Error opening memory map file: " + _base_filename); 
Controller.g_Logger.error("Error creating memory map file: " + e.getMessage()); 
e.printStackTrace(); 
Clear(); 
    if (_ioChannel != null) _ioChannel.close(); 
    if (_ioFile != null) _ioFile.close(); 
if (f != null) f.delete(); 
throw e; 
} 

我得到的標題提到的錯誤後,我分配在第二或第三緩衝。

我認爲這是與可用的連續內存有關,所以嘗試使用不同的頁面大小和數量,但沒有獲得整體收益。

究竟是什麼「沒有足夠的存儲空間來處理這個命令」意思是什麼,如果有的話,我能做些什麼?

我認爲MappedByteBuffers的重點在於能夠處理比您可以放在堆上更大的結構,並將它們當作它們在內存中一樣對待。

任何線索?

編輯:

針對以下(@adsk)的答案,我改變了我的代碼,所以我從來沒有多單主動MappedByteBuffer更在任何一個時間。當我引用當前未映射的文件的某個區域時,我會丟棄現有的地圖並創建一個新的地圖。在大約3次地圖操作後,我仍然遇到同樣的錯誤。

用GC引用的錯誤並未收集MappedByteBuffers,但在JDK 1.5中似乎仍然存在問題。

+0

之前,你的結論是錯誤的GC仍然存在,需要通過一個內存使用分析器來運行你的代碼,以確保其不會掛到字節緩衝區引用。 – 2009-12-18 11:03:08

回答

3

我認爲MappedByteBuffers的重點在於能夠處理比堆放的結構更大的結構,並將它們當作它們在內存中處理。

不是。這個想法是/允許你解決超過2 ** 31雙打的問題......假設你有足夠的內存,並且使用64位JVM。

(我假設這是一個this question的後續問題。)

編輯:很明顯,需要更多的解釋。

有一些限制發揮作用。

  1. Java有一個基本的限制是,length屬性的陣列,和數組索引具有類型int。這與int被簽名並且數組不能具有負值的事實相結合意味着最大可能的數組可以具有2**31元素。此限制適用於32位和64位JVM。它是Java語言的基礎部分...就像char值從065535這一事實。

  2. 使用32位JVM將(理論上)2**32的上限設置爲JVM可尋址的字節數。這包括您使用的整個堆,您的代碼和庫類,JVM的本地代碼核心,用於映射緩衝區的內存......一切。 (事實上​​,根據你的平臺,操作系統可能給你大大低於2**32字節,如果地址空間。)

  3. 你給java命令行上的參數確定多少堆內存,JVM將允許您的應用程序使用。映射到使用MappedByteBuffer對象的內存不計入此項。

  4. 操作系統將爲您提供的內存量取決於(在Linux/UNIX上)交換空間總量配置,「進程」限制等。類似的限制可能適用於Windows。當然,如果主機操作系統支持64位,則只能運行64位JVM,並且您使用的是64位硬件。 (如果你有奔騰,你很幸運。)

  5. 最後,系統中物理內存的數量會發揮作用。從理論上講,您可以讓您的JVM使用比機器物理內存大很多倍的堆等。實際上,這是一個壞主意。如果你過度分配虛擬內存,你的系統將會崩潰,應用程序的性能也會下降。

的帶走是這樣的:

  • 如果使用32位JVM,你可能被限制在某處2**32字節之間2**31和可尋址內存。無論您使用數組還是映射的緩衝區,這個空間都可以用於最大爲2**292**30雙打之間的空間。

  • 如果您使用64位JVM,則可以表示一個2**31加倍的單個陣列。映射緩衝區的理論極限爲2**63字節或2**61雙精度,但實際極限大致爲您機器的物理內存量。

+0

如果我有一個64位的JVM,我還需要幫助解決超過2^31雙打? – Simon 2009-12-18 09:34:19

+0

是的。問題是,當您使用32位JVM時,所有內容都必須適合那些2 ** 32個字節。如果不適合,則不能將其用作內存數據結構。 – 2009-12-18 10:16:24

+0

我顯然有點黯淡。我有一個大文件(比2^32大得多),我在32位環境(JVM/OS /等)中查看它。我的MappedByteBuffers永遠不會超過(Integer.MAX_VALUE/256),這意味着我永遠不會直接處理大於int可處理的任何事物。第一對mappng工作正常,後來失敗。我不明白什麼?應該如何使用MappedByteBuffer?如果它永遠不能解決比內存更大的東西,那麼它有什麼意義呢? – Simon 2009-12-18 10:23:53

1

當內存映射文件時,32位VM中的地址空間可能會用完。即使文件被映射成小塊並且這些ByteBuffers不再可達,也會發生這種情況。原因是GC從未開始釋放緩衝區。

參考的bug在http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6417205

+0

請參閱上面的編輯... – Simon 2009-12-18 10:16:08

+0

請注意,Sun >>認爲錯誤是固定的,並且只有一條評論表明它不是。此評論可能僅僅是評論者應用程序中的一個錯誤,它掛在對ByteBuffers的引用上,導致它們不符合垃圾回收的條件。 – 2009-12-18 11:01:11

+0

請注意,該錯誤在很久以前被修復了。如果您仍在運行受影響的JVM ...您不應該這樣做! – 2015-02-03 03:43:34

相關問題