2010-07-09 43 views
8

我想使用MapMaker創建緩存大對象的映射,如果內存不足,應從緩存中刪除這些大對象, 。 這個小演示程序似乎很好地工作:使用MapMaker創建緩存

public class TestValue { 
    private final int id; 
    private final int[] data = new int[100000]; 

    public TestValue(int id) { 
     this.id = id; 
    } 

    @Override 
    protected void finalize() throws Throwable { 
     super.finalize(); 
     System.out.println("finalized"); 
    } 
} 


public class Main { 

    private ConcurrentMap<Integer, TestValue> cache; 
    MemoryMXBean memoryBean; 

    public Main() { 
     cache = new MapMaker() 
       .weakKeys() 
       .softValues() 
       .makeMap(); 
     memoryBean = ManagementFactory.getMemoryMXBean(); 
    } 

    public void test() { 
     int i = 0; 
     while (true) { 
      System.out.println("Etntries: " + cache.size() + " heap: " 
       + memoryBean.getHeapMemoryUsage() + " non-heap: " 
       + memoryBean.getNonHeapMemoryUsage()); 
      for (int j = 0; j < 10; j++) { 
       i++; 
       TestValue t = new TestValue(i); 
       cache.put(i, t); 
      } 
      try { 
       Thread.sleep(100); 
      } catch (InterruptedException ex) { 
      } 
     } 
    } 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) { 
     Main m = new Main(); 
     m.test(); 
    } 

} 

然而,當我做同樣的事情在我的實際應用中,條目 基本上從緩存,儘快爲他們添加刪除。在我的真實 應用程序中,我也使用整數作爲鍵,並且緩存值爲 從包含某些數據的磁盤讀取的歸檔塊。據I 明白,只要不再使用 就會被垃圾收集垃圾收集,所以這似乎是有意義的,因爲這些鍵是弱的 引用。如果我創建的地圖是這樣的:

data = new MapMaker() 
      .softValues() 
      .makeMap(); 

的條目是從來沒有垃圾收集和我在測試程序得到了內存不足的錯誤 。 TestValue條目 上的finalize方法永遠不會被調用。如果我改變了測試方法如下:

public void test() { 
    int i = 0; 
    while (true) { 
     for (final Entry<Integer, TestValue> entry : 
      data.entrySet()) { 
      if (entry.getValue() == null) { 
       data.remove(entry.getKey()); 
      } 
     } 
     System.out.println("Etntries: " + data.size() + " heap: " 
      + memoryBean.getHeapMemoryUsage() + " non-heap: " 
      + memoryBean.getNonHeapMemoryUsage()); 
     for (int j = 0; j < 10; j++) { 
      i++; 
      TestValue t = new TestValue(i); 
      data.put(i, t); 
     } 
     try { 
      Thread.sleep(100); 
     } catch (InterruptedException ex) { 
     } 
    } 
} 

條目從緩存中,並在測試值 對象終結被稱爲刪除,但過了一段時間我也得到一個內存不足, 錯誤。

所以我的問題是:什麼是正確的方式來使用MapMaker創建一個 地圖,可以用作緩存?爲什麼我的測試程序不能儘快刪除 條目,如果我使用weakKeys?是否有可能將 添加引用隊列到緩存映射?

+0

有人可以編輯代碼,使其更容易閱讀? – nanda 2010-07-09 10:27:34

+0

我對此有點驚訝。我已經使用'softValues'完全相同的方式,並且它工作正常,當內存不足時,SoftReference被清除。 – finnw 2010-07-17 00:27:33

回答

3

弱鍵似乎是一個錯誤。嘗試使用強鍵,因爲它們是整數。

+0

我試過了,它在我創建一個新對象並將其添加到緩存之前調用System.gc()時有效。如果我不這樣做,我遲早會得到一個內存不足的例外。這是正確的方法,還是你推薦別的? – Michael 2010-07-09 12:32:57

+0

你有兩個版本的TestValue,一個擁有一個大數組,另一個只擁有一個int。你在用大陣列測試嗎?否則,GC可能無法釋放足夠的內存。 – 2010-07-09 12:47:16

8

有很多事情可能會發生,但對於使用軟值的測試程序:即使您有尚未被垃圾收集的SoftReference,也可能會發生OutOfMemoryError。需要重申的是,即使您有尚未清除的SoftReference,也可能會發生OutOfMemoryError。

SoftReferences有點奇怪,請參閱http://jeremymanson.blogspot.com/2009/07/how-hotspot-decides-to-clear_07.html瞭解當前機制。可能在您的測試案例中,GC沒有時間做兩個完整的GC。

當您使用弱鍵時,CG立即清除它們,並且不必等待完整的GC暫停。 (B/C在WeakReferences正在積極收集。)

在我看來,如果你想與整數密鑰的內存敏感的緩存,我認爲以下是合適的:

data = new MapMaker().softValues().makeMap(); 

您可以輕鬆地進行一個拋出OutOfMemoryError的測試程序,但是如果你真正的應用程序有一定的表現,並且沒有受到太大的壓力,那麼你可能會好起來的。 SoftReferences很難找到正確的。

如果您需要使用System.gc()避免內存不足,我建議您切換到固定最大大小的LRU映射(例如,請參閱java.util.LinkedHashMap的javadoc。 )它並不是併發的,但我希望它最終能夠讓您獲得更好的吞吐量,而不是要求系統在一堆額外的時間內完成全部暫停垃圾回收。

噢,還有關於整數鍵和弱鍵()的最後說明:當使用弱鍵或軟鍵時,MapMaker對鍵使用標識比較,這很難正確執行。見證如下:

Map<Integer,String> map = new MapMaker().weakKeys().makeMap(); 
Integer a = new Integer(1); 
Integer b = new Integer(1); 
Integer c = 1; //auto box 
Integer d = 1; //auto box 
map.put(a, "A"); 
map.put(b, "B"); 
map.put(c,"C"); 
map.put(d,"D"); 
map.size() // size is 3; 

祝你好運。

+0

+1,因爲我沒有意識到你可以在所有的SoftReference被gc'ed之前得到一個OutOfMemoryError。 – 2010-08-06 17:06:27