Hello,
I want to use MapMaker to create a map that caches large objects, which should be removed from the cache if there is not enough memory. This little demo program seems to work fine:
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();
}
}
However, when I do the same thing in my real application, entries are basically removed from the cache as soon as they are added. In my real application, I also use integers as keys, and the cached values are archive blocks read from disk that contains some data. As far as I understand, weak-references are garbage-collected as soon as they are no longer used, so this seems to make sense because the keys are weak references. If I create the map like this:
data = new MapMaker()
.softValues()
.makeMap();
The entries are never garbage-collected and I get an out-of-memory error in my test program. The finalize method on the TestValue entries is never called. If I change the test method to the following:
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) {
}
}
}
entries are removed from the cache and the finalizer on the TestValue objects is called, but after a while I also get an out-of-memory error.
So my question is: what is the right way to use MapMaker to create a map that can be used as a cache? Why does my test program not remove the entries as soon as possible if I use weakKeys? Is it possible to add a reference queue to the cache map?