I just had an interview, and I was asked to create a memory leak with Java.
Needless to say, I felt pretty dumb having no clue on how to eve
Another way to create potentially huge memory leaks is to hold references to Map.Entry
of a TreeMap
.
It is hard to asses why this applies only to TreeMap
s, but by looking at the implementation the reason might be that: a TreeMap.Entry
stores references to its siblings, therefore if a TreeMap
is ready to be collected, but some other class holds a reference to any of its Map.Entry
, then the entire Map will be retained into memory.
Real-life scenario:
Imagine having a db query that returns a big TreeMap
data structure. People usually use TreeMap
s as the element insertion order is retained.
public static Map pseudoQueryDatabase();
If the query was called lots of times and, for each query (so, for each Map
returned) you save an Entry
somewhere, the memory would constantly keep growing.
Consider the following wrapper class:
class EntryHolder {
Map.Entry entry;
EntryHolder(Map.Entry entry) {
this.entry = entry;
}
}
Application:
public class LeakTest {
private final List holdersCache = new ArrayList<>();
private static final int MAP_SIZE = 100_000;
public void run() {
// create 500 entries each holding a reference to an Entry of a TreeMap
IntStream.range(0, 500).forEach(value -> {
// create map
final Map map = pseudoQueryDatabase();
final int index = new Random().nextInt(MAP_SIZE);
// get random entry from map
for (Map.Entry entry : map.entrySet()) {
if (entry.getValue().equals(index)) {
holdersCache.add(new EntryHolder(entry));
break;
}
}
// to observe behavior in visualvm
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
public static Map pseudoQueryDatabase() {
final Map map = new TreeMap<>();
IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
return map;
}
public static void main(String[] args) throws Exception {
new LeakTest().run();
}
}
After each pseudoQueryDatabase()
call, the map
instances should be ready for collection, but it won't happen, as at least one Entry
is stored somewhere else.
Depending on your jvm
settings, the application may crash in the early stage due to a OutOfMemoryError
.
You can see from this visualvm
graph how the memory keeps growing.
The same does not happen with a hashed data-structure (HashMap
).
This is the graph when using a HashMap
.
The solution? Just directly save the key / value (as you probably already do) rather than saving the Map.Entry
.
I have written a more extensive benchmark here.