2008-10-24 40 views
11

我試圖插入大約50,000個對象(因此50,000個密鑰)到java.util.HashMap<java.awt.Point, Segment>。但是,我不斷收到OutOfMemory異常。 (Segment是我自己的班級 - 體重非常輕 - 一個String字段,以及3個int字段)。無論是在免費的RAM和硬盤空間用於虛擬內存 -爲什麼在將50,000個對象插入到HashMap中時會出現OutOfMemoryError?

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at java.util.HashMap.resize(HashMap.java:508) 
    at java.util.HashMap.addEntry(HashMap.java:799) 
    at java.util.HashMap.put(HashMap.java:431) 
    at bus.tools.UpdateMap.putSegment(UpdateMap.java:168)

,因爲我看到有大量內存的機器上可用這似乎挺可笑的。

是否有可能Java運行時有一些嚴格的內存要求?我可以增加這些嗎?

HashMap有一些奇怪的限制嗎?我將不得不執行我自己的?還有其他類別值得關注嗎?

(我一個Intel機器有2GB RAM上運行的Java 5下OS X 10.5。)

回答

20

可以增加通過傳遞-Xmx128m(其中,128是兆字節的數量)到Java堆大小的最大值。我不記得默認尺寸,但它讓我覺得這是一個相當小的東西。

您可以通過使用Runtime類以編程方式檢查有多少內存可用。

// Get current size of heap in bytes 
long heapSize = Runtime.getRuntime().totalMemory(); 

// Get maximum size of heap in bytes. The heap cannot grow beyond this size. 
// Any attempt will result in an OutOfMemoryException. 
long heapMaxSize = Runtime.getRuntime().maxMemory(); 

// Get amount of free memory within the heap in bytes. This size will increase 
// after garbage collection and decrease as new objects are created. 
long heapFreeSize = Runtime.getRuntime().freeMemory(); 

(來自實施例Java Developers Almanac

這也部分地解決Frequently Asked Questions About the Java HotSpot VM,並在Java 6 GC Tuning page

+0

如何確定當前的尺寸,以便我瞭解未來?謝謝! – 2008-10-24 19:57:26

+0

非常奇怪,雖然你有這樣的小內存可用,你不能添加50000個小對象到散列。聽起來不像那麼多。 – 2008-10-24 19:59:07

+0

謝謝!把它抽到2048MB,我的程序終於完成了!哈哈。哇。 – 2008-10-24 20:05:22

2

你可能需要啓動Java時,設置標誌-Xmx512m或一些較大的數字。我認爲64mb是默認值。

修改爲添加: 在找出對象實際使用的分析器的內存量之後,您可能需要查看弱引用或軟引用,以確保您不會意外地持有一些內存中的人質從垃圾收集器中,當你不再使用它們時。

1

隱含在這些答案中,Java具有固定的內存大小,並且不會超出配置的最大堆大小。這不像C說的那樣,它只受到它運行的機器的約束。

1

默認情況下,JVM使用有限的堆空間。限制取決於JVM的實現,並不清楚您使用的是什麼JVM。在Windows以外的操作系統上,具有2 Gb或更多計算機的32位Sun JVM將使用缺省最大堆大小,即物理內存的1/4,或512 Mb。但是,「客戶端」模式JVM的默認值僅爲64 Mb最大堆大小,這可能是您遇到的問題。其他供應商的JVM可能會選擇不同的默認值。

當然,您可以明確指定堆限制,-Xmx<NN>m選項爲java,其中<NN>是堆的兆字節數。

作爲一個粗略的猜測,你的散列表應該只使用大約16 Mb,所以在堆上必須有一些其他的大對象。如果您可以在TreeMap中使用Comparable密鑰,那將節省一些內存。

請參閱"Ergonomics in the 5.0 JVM"瞭解更多詳情。

3

如果事先知道對象的數量,另一件要嘗試的就是使用HashMap(int capacity,double loadfactor)構造函數,而不是使用默認值(16,0.75)的默認no-arg構造函數。如果HashMap中元素的數量超過(capacity * loadfactor),那麼HashMap中的底層數組將被調整爲下一個2的冪,並且該表將被重新映射。這個數組也需要一個連續的內存區域,例如,如果你從32768增加到65536大小的數組,你將需要256kB的內存空間。爲了避免額外的分配和重新哈希處罰,從一開始就使用一個更大的哈希表。它也會減少你不會有足夠大的內存區域以適應地圖的可能性。

3

實現通常由數組支持。數組是固定大小的內存塊。哈希映射實現首先將數據存儲在一個給定容量的數組中,比如100個對象。

如果它填滿了數組並且不停地添加對象,地圖需要祕密增加它的數組大小。由於數組是固定的,所以它通過在內存中創建一個全新的數組,以及稍大的當前數組來實現。這被稱爲增長陣列。然後,舊數組中的所有項都被複制到新數組中,並且舊數組被解引用,希望它會被垃圾收集並在某個時刻釋放內存。

通常情況下,通過將項目複製到更大陣列來增加地圖容量的代碼是造成此類問題的原因。有「愚蠢的」實現和聰明的實現,它們使用增長或加載因子,根據舊數組的大小來確定新數組的大小。有些實現會隱藏這些參數,有些則不會,所以您無法始終設置它們。問題是,當你無法設置它時,它會選擇一些默認的加載因子,比如2.因此新陣列的大小是舊的兩倍。現在你所謂的50k地圖有一個100k的支持數組。

看看是否可以將負載係數降至0.25或其他值。這會導致更多的哈希映射衝突,這會影響性能,但是您正在遇到內存瓶頸並需要這樣做。

使用這個構造:

http://java.sun.com/javase/6/docs/api/java/util/HashMap.html#HashMap(int,浮動))

1

Java堆空間是默認的限制,但仍然聽起來極端(雖然有多大的50000段)

?我懷疑你還有其他一些問題,比如集合中的數組越來越大,因爲所有東西都被分配到同一個「槽」(當然也會影響性能)。但是,如果你的觀點是均勻分佈的,這似乎不太可能。

我想知道爲什麼你使用HashMap而不是TreeMap?即使點是二維的,你可以用比較函數對它們進行子類化,然後做log(n)查找。

7

有人建議改變HashMap的參數來加強內存需求。我建議的措施,而不是猜測;它可能是導致OOME的其他內容。特別是,我建議使用NetBeans ProfilerVisualVM(它隨Java 6一起提供,但我看到你被Java 5困住了)。

1

隨機想法:與HashMap關聯的哈希桶不是特別有效的內存。您可能想要嘗試使用TreeMap作爲替代方案,並查看它是否仍能提供足夠的性能。

相關問題