java 內存緩存 Caffeine 的一點試驗

今天試驗了一把java新的內存緩存框架Caffeine,作為開發來講入門還是很輕鬆的。本著多試驗一些場景,防止出現未知問題的目的,我嘗試了一下Caffeine框架對於maximumSize的處理。

java 內存緩存 Caffeine 的一點試驗

先說一下我看到Caffeine框架maximumSize()這個appi的理解,第一眼看上去就是認為,這是控制總容量的,超過這個容量之後,會觸發緩存失效,清除掉部分緩存(不管是根據LRU還是LFU等),總之緩存的總容量是不會超過這個api設置的閾值的。通過我的實驗,發現實際情況並沒有那麼簡單……

為了簡單清晰,我把代碼拆解為最簡單的方式:

<code>Cache<string> myCache = Caffeine.newBuilder()
.initialCapacity(2)
.maximumSize(4)
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
System.out.println("初始點:"+myCache.asMap().size());
System.out.println("初始點:"+myCache.asMap());
myCache.put("1", "1");
myCache.getIfPresent("1");
myCache.put("2", "1");
myCache.put("3", "1");
myCache.getIfPresent("1");
myCache.put("4", "1");
myCache.put("5", "1");
myCache.put("6", "1");
myCache.getIfPresent("1");
myCache.put("7", "1");
myCache.put("8", "1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {

}
System.out.println("放置8個後:"+myCache.asMap().size());
System.out.println("放置8個後:"+myCache.asMap());
myCache.getIfPresent("1");
myCache.put("9", "1");
myCache.getIfPresent("9");
myCache.getIfPresent("9");

myCache.getIfPresent("9");
myCache.put("10", "1");
myCache.put("11", "1");
myCache.put("12", "1");
myCache.put("13", "1");
myCache.put("14", "1");
myCache.put("15", "1");
myCache.put("16", "1");
myCache.put("17", "1");
System.out.println("放置17個後:"+myCache.asMap().size());
System.out.println("放置17個後:"+myCache.asMap());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {

}
System.out.println("放置17個後-1:"+myCache.asMap().size());
System.out.println("放置17個後-1:"+myCache.asMap());
System.out.println("放置17個後-2:"+myCache.asMap().size());
System.out.println("放置17個後-2:"+myCache.asMap());
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("緩存超時後:"+myCache.asMap().size());
System.out.println("緩存超時後:"+myCache.asMap());

myCache.put("1", "1");
myCache.put("2", "1");
myCache.put("3", "1");
myCache.put("4", "1");
myCache.put("5", "1");
myCache.put("6", "1");
myCache.put("7", "1");
myCache.put("8", "1");
myCache.put("9", "1");
myCache.put("10", "1");
myCache.put("11", "1");
myCache.put("12", "1");
System.out.println("第二次放置12個後-pre:"+myCache.asMap().size());
System.out.println("第二次放置12個後-pre:"+myCache.asMap());
myCache.put("13", "1");
myCache.put("14", "1");
myCache.put("15", "1");

myCache.put("16", "1");
myCache.put("17", "1");
System.out.println("第二次放置17個後:"+myCache.asMap().size());
System.out.println("第二次放置17個後:"+myCache.asMap());
System.out.println("第二次放置17個後-1:"+myCache.asMap().size());
System.out.println("第二次放置17個後-1:"+myCache.asMap());
System.out.println("第二次放置17個後-2:"+myCache.asMap().size());
System.out.println("第二次放置17個後-2:"+myCache.asMap());
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二次緩存超時後:"+myCache.asMap().size());
System.out.println("第二次緩存超時後:"+myCache.asMap());/<string>/<code>

其中一次的結果為:

<code>初始點:0
初始點:{}
放置8個後:4
放置8個後:{3=1, 6=1, 7=1, 8=1}
放置17個後:13
放置17個後:{11=1, 12=1, 13=1, 14=1, 15=1, 16=1, 17=1, 3=1, 6=1, 7=1, 8=1, 9=1, 10=1}
放置17個後-1:4
放置17個後-1:{12=1, 13=1, 17=1, 9=1}
放置17個後-2:4
放置17個後-2:{12=1, 13=1, 17=1, 9=1}
緩存超時後:4
緩存超時後:{}
第二次放置12個後-pre:6
第二次放置12個後-pre:{11=1, 12=1, 9=1, 10=1}
第二次放置17個後:9
第二次放置17個後:{11=1, 12=1, 13=1, 17=1, 9=1, 10=1}

第二次放置17個後-1:4
第二次放置17個後-1:{12=1, 17=1, 9=1, 10=1}
第二次放置17個後-2:4
第二次放置17個後-2:{12=1, 17=1, 9=1, 10=1}
第二次緩存超時後:4
第二次緩存超時後:{}
/<code>
java 內存緩存 Caffeine 的一點試驗

從上面這一次的運行結果可以看出,maximumSize()確實會生效,但是緩存存在實際數據容量遠大於maximumSize閾值的情況的。如果你緩存的數據對象很大,而且可能會很多的時候,要提前預估好內存空間,防止內存溢出,雖然Caffeine會異步去清除超過容量部分的緩存,但我們還是要考慮超量佔用內存的情況。

通過後續的研究,我又加了一個緩存失效被移除的監聽,其他部分不變,初始緩存的代碼改動如下:

<code>Cache<string> myCache = Caffeine.newBuilder()
.initialCapacity(2)
.maximumSize(4)
.expireAfterWrite(5, TimeUnit.SECONDS)
.removalListener(((key, value, cause) -> {
System.out.println("清除:" + String.join(":", key.toString(), cause.toString()));
}))
.build();/<string>/<code>

運行之後,其中一次結果如下:

<code>初始點:0
初始點:{}
清除:6:SIZE
清除:5:SIZE
清除:1:SIZE
清除:2:SIZE
放置8個後:4
放置8個後:{3=1, 4=1, 7=1, 8=1}
放置17個後:13
放置17個後:{11=1, 12=1, 13=1, 14=1, 15=1, 16=1, 17=1, 3=1, 4=1, 7=1, 8=1, 9=1, 10=1}

清除:16:SIZE
清除:15:SIZE
清除:14:SIZE
清除:3:SIZE
清除:4:SIZE
清除:11:SIZE
清除:10:SIZE
清除:7:SIZE
清除:8:SIZE
放置17個後-1:4
放置17個後-1:{12=1, 13=1, 17=1, 9=1}
放置17個後-2:4
放置17個後-2:{12=1, 13=1, 17=1, 9=1}
緩存超時後:4
緩存超時後:{}
清除:9:EXPIRED
清除:7:SIZE
清除:5:SIZE
清除:4:SIZE
清除:3:SIZE
清除:6:SIZE
清除:17:SIZE
清除:1:SIZE
清除:2:SIZE
第二次放置12個後-pre:6
第二次放置12個後-pre:{11=1, 12=1, 9=1, 10=1}
清除:8:SIZE
第二次放置17個後:9
清除:12:EXPIRED
第二次放置17個後:{11=1, 12=1, 13=1, 14=1, 15=1, 16=1, 17=1, 9=1, 10=1}
第二次放置17個後-1:6
第二次放置17個後-1:{11=1, 12=1, 17=1, 9=1, 10=1}
第二次放置17個後-2:5
清除:13:EXPIRED
清除:15:SIZE

第二次放置17個後-2:{11=1, 12=1, 17=1, 9=1, 10=1}
清除:16:SIZE
清除:11:SIZE
清除:14:SIZE
清除:13:SIZE
第二次緩存超時後:4
第二次緩存超時後:{}/<code>

可以看到每次數據移除的原因是RemovalCause.SIZE。

<code>public enum RemovalCause {
EXPLICIT {
public boolean wasEvicted() {
return false;
}
},
REPLACED {
public boolean wasEvicted() {
return false;
}
},
COLLECTED {
public boolean wasEvicted() {
return true;
}
},
EXPIRED {
public boolean wasEvicted() {
return true;
}
},
SIZE {
public boolean wasEvicted() {
return true;
}
};/<code>

關於Caffeine失效策略和其他的用法我還在研究中,以後會繼續分享我的發現。如果你發現我寫的不對或者有什麼補充,歡迎評論或者私信我,我都會看的。


java 內存緩存 Caffeine 的一點試驗



分享到:


相關文章: