How can I create a memory leak in Java?

后端 未结 30 1775
没有蜡笔的小新
没有蜡笔的小新 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:51

    Static field holding object reference [esp final field]

    class MemorableClass {
        static final ArrayList list = new ArrayList(100);
    }
    

    Calling String.intern() on lengthy String

    String str=readString(); // read lengthy string any source db,textbox/jsp etc..
    // This will place the string in memory pool from which you can't remove
    str.intern();
    

    (Unclosed) open streams ( file , network etc... )

    try {
        BufferedReader br = new BufferedReader(new FileReader(inputFile));
        ...
        ...
    } catch (Exception e) {
        e.printStacktrace();
    }
    

    Unclosed connections

    try {
        Connection conn = ConnectionFactory.getConnection();
        ...
        ...
    } catch (Exception e) {
        e.printStacktrace();
    }
    

    Areas that are unreachable from JVM's garbage collector, such as memory allocated through native methods

    In web applications, some objects are stored in application scope until the application is explicitly stopped or removed.

    getServletContext().setAttribute("SOME_MAP", map);
    

    Incorrect or inappropriate JVM options, such as the noclassgc option on IBM JDK that prevents unused class garbage collection

    See IBM jdk settings.

    0 讨论(0)
  • 2020-11-21 22:53

    Here's a simple/sinister one via http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29.

    public class StringLeaker
    {
        private final String muchSmallerString;
    
        public StringLeaker()
        {
            // Imagine the whole Declaration of Independence here
            String veryLongString = "We hold these truths to be self-evident...";
    
            // The substring here maintains a reference to the internal char[]
            // representation of the original string.
            this.muchSmallerString = veryLongString.substring(0, 1);
        }
    }
    

    Because the substring refers to the internal representation of the original, much longer string, the original stays in memory. Thus, as long as you have a StringLeaker in play, you have the whole original string in memory, too, even though you might think you're just holding on to a single-character string.

    The way to avoid storing an unwanted reference to the original string is to do something like this:

    ...
    this.muchSmallerString = new String(veryLongString.substring(0, 1));
    ...
    

    For added badness, you might also .intern() the substring:

    ...
    this.muchSmallerString = veryLongString.substring(0, 1).intern();
    ...
    

    Doing so will keep both the original long string and the derived substring in memory even after the StringLeaker instance has been discarded.

    0 讨论(0)
  • 2020-11-21 22:54

    Everyone always forgets the native code route. Here's a simple formula for a leak:

    1. Declare native method.
    2. In native method, call malloc. Don't call free.
    3. Call the native method.

    Remember, memory allocations in native code come from the JVM heap.

    0 讨论(0)
  • 2020-11-21 22:54

    I don't think anyone has said this yet: you can resurrect an object by overriding the finalize() method such that finalize() stores a reference of this somewhere. The garbage collector will only be called once on the object so after that the object will never destroyed.

    0 讨论(0)
  • 2020-11-21 22:56

    You can create a moving memory leak by creating a new instance of a class in that class's finalize method. Bonus points if the finalizer creates multiple instances. Here's a simple program that leaks the entire heap in sometime between a few seconds and a few minutes depending on your heap size:

    class Leakee {
        public void check() {
            if (depth > 2) {
                Leaker.done();
            }
        }
        private int depth;
        public Leakee(int d) {
            depth = d;
        }
        protected void finalize() {
            new Leakee(depth + 1).check();
            new Leakee(depth + 1).check();
        }
    }
    
    public class Leaker {
        private static boolean makeMore = true;
        public static void done() {
            makeMore = false;
        }
        public static void main(String[] args) throws InterruptedException {
            // make a bunch of them until the garbage collector gets active
            while (makeMore) {
                new Leakee(0).check();
            }
            // sit back and watch the finalizers chew through memory
            while (true) {
                Thread.sleep(1000);
                System.out.println("memory=" +
                        Runtime.getRuntime().freeMemory() + " / " +
                        Runtime.getRuntime().totalMemory());
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-21 22:56

    Another way to create potentially huge memory leaks is to hold references to Map.Entry<K,V> 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<String, Integer> 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<String, Integer> entry;
    
        EntryHolder(Map.Entry<String, Integer> entry) {
            this.entry = entry;
        }
    }
    

    Application:

    public class LeakTest {
    
        private final List<EntryHolder> 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<String, Integer> map = pseudoQueryDatabase();
    
                final int index = new Random().nextInt(MAP_SIZE);
    
                // get random entry from map
                for (Map.Entry<String, Integer> 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<String, Integer> pseudoQueryDatabase() {
            final Map<String, Integer> 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.

    0 讨论(0)
提交回复
热议问题