提交 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> {
*
* @param maxMemory the maximum memory to use (1 or larger)
*/
public CacheLongKeyLIRS(int maxMemory) {
public CacheLongKeyLIRS(long maxMemory) {
this(maxMemory, 16, 8);
}
......@@ -543,11 +543,6 @@ public class CacheLongKeyLIRS<V> {
*/
private final int mask;
/**
* The LIRS stack size.
*/
private int stackSize;
/**
* The stack of recently referenced elements. This includes all hot
* entries, and the recently referenced cold entries. Resident cold
......@@ -558,6 +553,11 @@ public class CacheLongKeyLIRS<V> {
*/
private final Entry<V> stack;
/**
* The number of entries in the stack.
*/
private int stackSize;
/**
* The queue of resident cold entries.
* <p>
......@@ -800,6 +800,10 @@ public class CacheLongKeyLIRS<V> {
old = e.value;
remove(key, hash);
}
if (memory > maxMemory) {
// the new entry is too big to fit
return old;
}
e = new Entry<V>();
e.key = key;
e.value = value;
......@@ -808,9 +812,15 @@ public class CacheLongKeyLIRS<V> {
e.mapNext = entries[index];
entries[index] = e;
usedMemory += memory;
if (usedMemory > maxMemory && mapSize > 0) {
// an old entry needs to be removed
evict(e);
if (usedMemory > maxMemory) {
// old entries needs to be removed
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++;
// added entries are always added to the stack
......@@ -874,23 +884,22 @@ public class CacheLongKeyLIRS<V> {
* 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
* 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
// division by 32, that means if there are only 1/32 (3.125%) or
// less cold entries, a hot entry needs to become cold
while (queueSize <= (mapSize >>> 5) && stackSize > 0) {
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
// but at least one cold entry (the new one) must stay
while (usedMemory > maxMemory && queueSize > 1) {
while (usedMemory > maxMemory && queueSize > 0) {
Entry<V> e = queue.queuePrev;
usedMemory -= e.memory;
removeFromQueue(e);
......
......@@ -43,6 +43,7 @@ public class TestCacheLIRS extends TestBase {
testLimitHot();
testLimitNonResident();
testBadHashMethod();
testLimitMemory();
testScanResistance();
testRandomOperations();
}
......@@ -77,7 +78,7 @@ public class TestCacheLIRS extends TestBase {
private void testEdgeCases() {
CacheLIRS<Integer, Integer> test = createCache(1);
test.put(1, 10, 100);
assertEquals(10, test.get(1).intValue());
assertEquals(0, test.size());
try {
test.put(null, 10, 100);
fail();
......@@ -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() {
boolean log = false;
int size = 20;
......
......@@ -43,6 +43,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
testPruneStack();
testLimitHot();
testLimitNonResident();
testLimitMemory();
testScanResistance();
testRandomOperations();
}
......@@ -77,7 +78,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
private void testEdgeCases() {
CacheLongKeyLIRS<Integer> test = createCache(1);
test.put(1, 10, 100);
assertEquals(10, test.get(1).intValue());
assertEquals(0, test.size());
try {
test.put(1, null, 100);
fail();
......@@ -335,6 +336,24 @@ public class TestCacheLongKeyLIRS extends TestBase {
"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() {
boolean log = false;
int size = 20;
......
......@@ -64,7 +64,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
*
* @param maxMemory the maximum memory to use (1 or larger)
*/
public CacheLIRS(int maxMemory) {
public CacheLIRS(long maxMemory) {
this(maxMemory, 16, 8);
}
......@@ -470,11 +470,6 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
*/
private final int mask;
/**
* The LIRS stack size.
*/
private int stackSize;
/**
* The stack of recently referenced elements. This includes all hot
* entries, and the recently referenced cold entries. Resident cold
......@@ -485,6 +480,11 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
*/
private final Entry<K, V> stack;
/**
* The number of entries in the stack.
*/
private int stackSize;
/**
* The queue of resident cold entries.
* <p>
......@@ -724,6 +724,10 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
old = e.value;
remove(key, hash);
}
if (memory > maxMemory) {
// the new entry is too big to fit
return old;
}
e = new Entry<K, V>();
e.key = key;
e.value = value;
......@@ -732,9 +736,15 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
e.mapNext = entries[index];
entries[index] = e;
usedMemory += memory;
if (usedMemory > maxMemory && mapSize > 0) {
// an old entry needs to be removed
evict(e);
if (usedMemory > maxMemory) {
// old entries needs to be removed
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++;
// added entries are always added to the stack
......@@ -798,23 +808,22 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
* 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
* 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
// division by 32, that means if there are only 1/32 (3.125%) or
// less cold entries, a hot entry needs to become cold
while (queueSize <= (mapSize >>> 5) && stackSize > 0) {
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
// but at least one cold entry (the new one) must stay
while (usedMemory > maxMemory && queueSize > 1) {
while (usedMemory > maxMemory && queueSize > 0) {
Entry<K, V> e = queue.queuePrev;
usedMemory -= e.memory;
removeFromQueue(e);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论