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 的一点试验



分享到:


相關文章: