提交 d87244fa authored 作者: Thomas Mueller's avatar Thomas Mueller

The LIRS cache could grow larger than the allocated memory.

上级 26f02d9c
...@@ -62,7 +62,7 @@ public class CacheLongKeyLIRS<V> { ...@@ -62,7 +62,7 @@ public class CacheLongKeyLIRS<V> {
* *
* @param maxMemory the maximum memory to use (1 or larger) * @param maxMemory the maximum memory to use (1 or larger)
*/ */
public CacheLongKeyLIRS(int maxMemory) { public CacheLongKeyLIRS(long maxMemory) {
this(maxMemory, 16, 8); this(maxMemory, 16, 8);
} }
...@@ -543,11 +543,6 @@ public class CacheLongKeyLIRS<V> { ...@@ -543,11 +543,6 @@ public class CacheLongKeyLIRS<V> {
*/ */
private final int mask; private final int mask;
/**
* The LIRS stack size.
*/
private int stackSize;
/** /**
* The stack of recently referenced elements. This includes all hot * The stack of recently referenced elements. This includes all hot
* entries, and the recently referenced cold entries. Resident cold * entries, and the recently referenced cold entries. Resident cold
...@@ -558,6 +553,11 @@ public class CacheLongKeyLIRS<V> { ...@@ -558,6 +553,11 @@ public class CacheLongKeyLIRS<V> {
*/ */
private final Entry<V> stack; private final Entry<V> stack;
/**
* The number of entries in the stack.
*/
private int stackSize;
/** /**
* The queue of resident cold entries. * The queue of resident cold entries.
* <p> * <p>
...@@ -800,6 +800,10 @@ public class CacheLongKeyLIRS<V> { ...@@ -800,6 +800,10 @@ public class CacheLongKeyLIRS<V> {
old = e.value; old = e.value;
remove(key, hash); remove(key, hash);
} }
if (memory > maxMemory) {
// the new entry is too big to fit
return old;
}
e = new Entry<V>(); e = new Entry<V>();
e.key = key; e.key = key;
e.value = value; e.value = value;
...@@ -808,9 +812,15 @@ public class CacheLongKeyLIRS<V> { ...@@ -808,9 +812,15 @@ public class CacheLongKeyLIRS<V> {
e.mapNext = entries[index]; e.mapNext = entries[index];
entries[index] = e; entries[index] = e;
usedMemory += memory; usedMemory += memory;
if (usedMemory > maxMemory && mapSize > 0) { if (usedMemory > maxMemory) {
// an old entry needs to be removed // old entries needs to be removed
evict(e); evict();
// if the cache is full, the new entry is
// cold if possible
if (stackSize > 0) {
// the new cold entry is at the top of the queue
addToQueue(queue, e);
}
} }
mapSize++; mapSize++;
// added entries are always added to the stack // added entries are always added to the stack
...@@ -874,23 +884,22 @@ public class CacheLongKeyLIRS<V> { ...@@ -874,23 +884,22 @@ public class CacheLongKeyLIRS<V> {
* Evict cold entries (resident and non-resident) until the memory limit * Evict cold entries (resident and non-resident) until the memory limit
* is reached. The new entry is added as a cold entry, except if it is * is reached. The new entry is added as a cold entry, except if it is
* the only entry. * the only entry.
*
* @param newCold a new cold entry
*/ */
private void evict(Entry<V> newCold) { private void evict() {
do {
evictBlock();
} while (usedMemory > maxMemory);
}
private void evictBlock() {
// ensure there are not too many hot entries: right shift of 5 is // ensure there are not too many hot entries: right shift of 5 is
// division by 32, that means if there are only 1/32 (3.125%) or // division by 32, that means if there are only 1/32 (3.125%) or
// less cold entries, a hot entry needs to become cold // less cold entries, a hot entry needs to become cold
while (queueSize <= (mapSize >>> 5) && stackSize > 0) { while (queueSize <= (mapSize >>> 5) && stackSize > 0) {
convertOldestHotToCold(); convertOldestHotToCold();
} }
if (stackSize > 0) {
// the new cold entry is at the top of the queue
addToQueue(queue, newCold);
}
// the oldest resident cold entries become non-resident // the oldest resident cold entries become non-resident
// but at least one cold entry (the new one) must stay while (usedMemory > maxMemory && queueSize > 0) {
while (usedMemory > maxMemory && queueSize > 1) {
Entry<V> e = queue.queuePrev; Entry<V> e = queue.queuePrev;
usedMemory -= e.memory; usedMemory -= e.memory;
removeFromQueue(e); removeFromQueue(e);
......
...@@ -43,6 +43,7 @@ public class TestCacheLIRS extends TestBase { ...@@ -43,6 +43,7 @@ public class TestCacheLIRS extends TestBase {
testLimitHot(); testLimitHot();
testLimitNonResident(); testLimitNonResident();
testBadHashMethod(); testBadHashMethod();
testLimitMemory();
testScanResistance(); testScanResistance();
testRandomOperations(); testRandomOperations();
} }
...@@ -77,7 +78,7 @@ public class TestCacheLIRS extends TestBase { ...@@ -77,7 +78,7 @@ public class TestCacheLIRS extends TestBase {
private void testEdgeCases() { private void testEdgeCases() {
CacheLIRS<Integer, Integer> test = createCache(1); CacheLIRS<Integer, Integer> test = createCache(1);
test.put(1, 10, 100); test.put(1, 10, 100);
assertEquals(10, test.get(1).intValue()); assertEquals(0, test.size());
try { try {
test.put(null, 10, 100); test.put(null, 10, 100);
fail(); fail();
...@@ -407,6 +408,24 @@ public class TestCacheLIRS extends TestBase { ...@@ -407,6 +408,24 @@ public class TestCacheLIRS extends TestBase {
} }
} }
private void testLimitMemory() {
CacheLIRS<Integer, Integer> test = createCache(4);
for (int i = 0; i < 5; i++) {
test.put(i, 10 * i, 1);
}
verify(test, "mem: 4 stack: 4 3 2 1 cold: 4 non-resident: 0");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(6, 60, 3);
verify(test, "mem: 4 stack: 6 3 cold: 6 non-resident:");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(7, 70, 3);
verify(test, "mem: 4 stack: 7 6 3 cold: 7 non-resident: 6");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(8, 80, 4);
verify(test, "mem: 4 stack: 8 cold: non-resident:");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
}
private void testScanResistance() { private void testScanResistance() {
boolean log = false; boolean log = false;
int size = 20; int size = 20;
......
...@@ -43,6 +43,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -43,6 +43,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
testPruneStack(); testPruneStack();
testLimitHot(); testLimitHot();
testLimitNonResident(); testLimitNonResident();
testLimitMemory();
testScanResistance(); testScanResistance();
testRandomOperations(); testRandomOperations();
} }
...@@ -77,7 +78,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -77,7 +78,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
private void testEdgeCases() { private void testEdgeCases() {
CacheLongKeyLIRS<Integer> test = createCache(1); CacheLongKeyLIRS<Integer> test = createCache(1);
test.put(1, 10, 100); test.put(1, 10, 100);
assertEquals(10, test.get(1).intValue()); assertEquals(0, test.size());
try { try {
test.put(1, null, 100); test.put(1, null, 100);
fail(); fail();
...@@ -335,6 +336,24 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -335,6 +336,24 @@ public class TestCacheLongKeyLIRS extends TestBase {
"cold: 19 non-resident: 18 17 16"); "cold: 19 non-resident: 18 17 16");
} }
private void testLimitMemory() {
CacheLongKeyLIRS<Integer> test = createCache(4);
for (int i = 0; i < 5; i++) {
test.put(i, 10 * i, 1);
}
verify(test, "mem: 4 stack: 4 3 2 1 cold: 4 non-resident: 0");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(6, 60, 3);
verify(test, "mem: 4 stack: 6 3 cold: 6 non-resident:");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(7, 70, 3);
verify(test, "mem: 4 stack: 7 6 3 cold: 7 non-resident: 6");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(8, 80, 4);
verify(test, "mem: 4 stack: 8 cold: non-resident:");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
}
private void testScanResistance() { private void testScanResistance() {
boolean log = false; boolean log = false;
int size = 20; int size = 20;
......
...@@ -64,7 +64,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -64,7 +64,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
* *
* @param maxMemory the maximum memory to use (1 or larger) * @param maxMemory the maximum memory to use (1 or larger)
*/ */
public CacheLIRS(int maxMemory) { public CacheLIRS(long maxMemory) {
this(maxMemory, 16, 8); this(maxMemory, 16, 8);
} }
...@@ -470,11 +470,6 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -470,11 +470,6 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
*/ */
private final int mask; private final int mask;
/**
* The LIRS stack size.
*/
private int stackSize;
/** /**
* The stack of recently referenced elements. This includes all hot * The stack of recently referenced elements. This includes all hot
* entries, and the recently referenced cold entries. Resident cold * entries, and the recently referenced cold entries. Resident cold
...@@ -485,6 +480,11 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -485,6 +480,11 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
*/ */
private final Entry<K, V> stack; private final Entry<K, V> stack;
/**
* The number of entries in the stack.
*/
private int stackSize;
/** /**
* The queue of resident cold entries. * The queue of resident cold entries.
* <p> * <p>
...@@ -724,6 +724,10 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -724,6 +724,10 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
old = e.value; old = e.value;
remove(key, hash); remove(key, hash);
} }
if (memory > maxMemory) {
// the new entry is too big to fit
return old;
}
e = new Entry<K, V>(); e = new Entry<K, V>();
e.key = key; e.key = key;
e.value = value; e.value = value;
...@@ -732,9 +736,15 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -732,9 +736,15 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
e.mapNext = entries[index]; e.mapNext = entries[index];
entries[index] = e; entries[index] = e;
usedMemory += memory; usedMemory += memory;
if (usedMemory > maxMemory && mapSize > 0) { if (usedMemory > maxMemory) {
// an old entry needs to be removed // old entries needs to be removed
evict(e); evict();
// if the cache is full, the new entry is
// cold if possible
if (stackSize > 0) {
// the new cold entry is at the top of the queue
addToQueue(queue, e);
}
} }
mapSize++; mapSize++;
// added entries are always added to the stack // added entries are always added to the stack
...@@ -798,23 +808,22 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -798,23 +808,22 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
* Evict cold entries (resident and non-resident) until the memory limit * Evict cold entries (resident and non-resident) until the memory limit
* is reached. The new entry is added as a cold entry, except if it is * is reached. The new entry is added as a cold entry, except if it is
* the only entry. * the only entry.
*
* @param newCold a new cold entry
*/ */
private void evict(Entry<K, V> newCold) { private void evict() {
do {
evictBlock();
} while (usedMemory > maxMemory);
}
private void evictBlock() {
// ensure there are not too many hot entries: right shift of 5 is // ensure there are not too many hot entries: right shift of 5 is
// division by 32, that means if there are only 1/32 (3.125%) or // division by 32, that means if there are only 1/32 (3.125%) or
// less cold entries, a hot entry needs to become cold // less cold entries, a hot entry needs to become cold
while (queueSize <= (mapSize >>> 5) && stackSize > 0) { while (queueSize <= (mapSize >>> 5) && stackSize > 0) {
convertOldestHotToCold(); convertOldestHotToCold();
} }
if (stackSize > 0) {
// the new cold entry is at the top of the queue
addToQueue(queue, newCold);
}
// the oldest resident cold entries become non-resident // the oldest resident cold entries become non-resident
// but at least one cold entry (the new one) must stay while (usedMemory > maxMemory && queueSize > 0) {
while (usedMemory > maxMemory && queueSize > 1) {
Entry<K, V> e = queue.queuePrev; Entry<K, V> e = queue.queuePrev;
usedMemory -= e.memory; usedMemory -= e.memory;
removeFromQueue(e); removeFromQueue(e);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论