How can I create a memory leak in Java?

后端 未结 30 1781
没有蜡笔的小新
没有蜡笔的小新 2020-11-21 22:26

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

30条回答
  •  灰色年华
    2020-11-21 22:56

    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 TreeMaps, 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 TreeMaps 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.

提交回复
热议问题