How can I create a memory leak in Java?

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

    You are able to make memory leak with sun.misc.Unsafe class. In fact this service class is used in different standard classes (for example in java.nio classes). You can't create instance of this class directly, but you may use reflection to do that.

    Code doesn't compile in Eclipse IDE - compile it using command javac (during compilation you'll get warnings)

    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import sun.misc.Unsafe;
    
    
    public class TestUnsafe {
    
        public static void main(String[] args) throws Exception{
            Class unsafeClass = Class.forName("sun.misc.Unsafe");
            Field f = unsafeClass.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            System.out.print("4..3..2..1...");
            try
            {
                for(;;)
                    unsafe.allocateMemory(1024*1024);
            } catch(Error e) {
                System.out.println("Boom :)");
                e.printStackTrace();
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-21 22:41

    Below there will be a non-obvious case where Java leaks, besides the standard case of forgotten listeners, static references, bogus/modifiable keys in hashmaps, or just threads stuck without any chance to end their life-cycle.

    • File.deleteOnExit() - always leaks the string, if the string is a substring, the leak is even worse (the underlying char[] is also leaked) - in Java 7 substring also copies the char[], so the later doesn't apply; @Daniel, no needs for votes, though.

    I'll concentrate on threads to show the danger of unmanaged threads mostly, don't wish to even touch swing.

    • Runtime.addShutdownHook and not remove... and then even with removeShutdownHook due to a bug in ThreadGroup class regarding unstarted threads it may not get collected, effectively leak the ThreadGroup. JGroup has the leak in GossipRouter.

    • Creating, but not starting, a Thread goes into the same category as above.

    • Creating a thread inherits the ContextClassLoader and AccessControlContext, plus the ThreadGroup and any InheritedThreadLocal, all those references are potential leaks, along with the entire classes loaded by the classloader and all static references, and ja-ja. The effect is especially visible with the entire j.u.c.Executor framework that features a super simple ThreadFactory interface, yet most developers have no clue of the lurking danger. Also a lot of libraries do start threads upon request (way too many industry popular libraries).

    • ThreadLocal caches; those are evil in many cases. I am sure everyone has seen quite a bit of simple caches based on ThreadLocal, well the bad news: if the thread keeps going more than expected the life the context ClassLoader, it is a pure nice little leak. Do not use ThreadLocal caches unless really needed.

    • Calling ThreadGroup.destroy() when the ThreadGroup has no threads itself, but it still keeps child ThreadGroups. A bad leak that will prevent the ThreadGroup to remove from its parent, but all the children become un-enumerateable.

    • Using WeakHashMap and the value (in)directly references the key. This is a hard one to find without a heap dump. That applies to all extended Weak/SoftReference that might keep a hard reference back to the guarded object.

    • Using java.net.URL with the HTTP(S) protocol and loading the resource from(!). This one is special, the KeepAliveCache creates a new thread in the system ThreadGroup which leaks the current thread's context classloader. The thread is created upon the first request when no alive thread exists, so either you may get lucky or just leak. The leak is already fixed in Java 7 and the code that creates thread properly removes the context classloader. There are few more cases (like ImageFetcher, also fixed) of creating similar threads.

    • Using InflaterInputStream passing new java.util.zip.Inflater() in the constructor (PNGImageDecoder for instance) and not calling end() of the inflater. Well, if you pass in the constructor with just new, no chance... And yes, calling close() on the stream does not close the inflater if it's manually passed as constructor parameter. This is not a true leak since it'd be released by the finalizer... when it deems it necessary. Till that moment it eats native memory so badly it can cause Linux oom_killer to kill the process with impunity. The main issue is that finalization in Java is very unreliable and G1 made it worse till 7.0.2. Moral of the story: release native resources as soon as you can; the finalizer is just too poor.

    • The same case with java.util.zip.Deflater. This one is far worse since Deflater is memory hungry in Java, i.e. always uses 15 bits (max) and 8 memory levels (9 is max) allocating several hundreds KB of native memory. Fortunately, Deflater is not widely used and to my knowledge JDK contains no misuses. Always call end() if you manually create a Deflater or Inflater. The best part of the last two: you can't find them via normal profiling tools available.

    (I can add some more time wasters I have encountered upon request.)

    Good luck and stay safe; leaks are evil!

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

    Maybe by using external native code through JNI?

    With pure Java, it is almost impossible.

    But that is about a "standard" type of memory leak, when you cannot access the memory anymore, but it is still owned by the application. You can instead keep references to unused objects, or open streams without closing them afterwards.

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

    What's a memory leak:

    • It's caused by a bug or bad design.
    • It's a waste of memory.
    • It gets worse over time.
    • The garbage collector cannot clean it.

    Typical example:

    A cache of objects is a good starting point to mess things up.

    private static final Map<String, Info> myCache = new HashMap<>();
    
    public void getInfo(String key)
    {
        // uses cache
        Info info = myCache.get(key);
        if (info != null) return info;
    
        // if it's not in cache, then fetch it from the database
        info = Database.fetch(key);
        if (info == null) return null;
    
        // and store it in the cache
        myCache.put(key, info);
        return info;
    }
    

    Your cache grows and grows. And pretty soon the entire database gets sucked into memory. A better design uses an LRUMap (Only keeps recently used objects in cache).

    Sure, you can make things a lot more complicated:

    • using ThreadLocal constructions.
    • adding more complex reference trees.
    • or leaks caused by 3rd party libraries.

    What often happens:

    If this Info object has references to other objects, which again have references to other objects. In a way you could also consider this to be some kind of memory leak, (caused by bad design).

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

    I came across a more subtle kind of resource leak recently. We open resources via class loader's getResourceAsStream and it happened that the input stream handles were not closed.

    Uhm, you might say, what an idiot.

    Well, what makes this interesting is: this way, you can leak heap memory of the underlying process, rather than from JVM's heap.

    All you need is a jar file with a file inside which will be referenced from Java code. The bigger the jar file, the quicker memory gets allocated.

    You can easily create such a jar with the following class:

    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipOutputStream;
    
    public class BigJarCreator {
        public static void main(String[] args) throws IOException {
            ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
            zos.putNextEntry(new ZipEntry("resource.txt"));
            zos.write("not too much in here".getBytes());
            zos.closeEntry();
            zos.putNextEntry(new ZipEntry("largeFile.out"));
            for (int i=0 ; i<10000000 ; i++) {
                zos.write((int) (Math.round(Math.random()*100)+20));
            }
            zos.closeEntry();
            zos.close();
        }
    }
    

    Just paste into a file named BigJarCreator.java, compile and run it from command line:

    javac BigJarCreator.java
    java -cp . BigJarCreator
    

    Et voilà: you find a jar archive in your current working directory with two files inside.

    Let's create a second class:

    public class MemLeak {
        public static void main(String[] args) throws InterruptedException {
            int ITERATIONS=100000;
            for (int i=0 ; i<ITERATIONS ; i++) {
                MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
            }
            System.out.println("finished creation of streams, now waiting to be killed");
    
            Thread.sleep(Long.MAX_VALUE);
        }
    
    }
    

    This class basically does nothing, but create unreferenced InputStream objects. Those objects will be garbage collected immediately and thus, do not contribute to heap size. It is important for our example to load an existing resource from a jar file, and size does matter here!

    If you're doubtful, try to compile and start the class above, but make sure to chose a decent heap size (2 MB):

    javac MemLeak.java
    java -Xmx2m -classpath .:big.jar MemLeak
    

    You will not encounter an OOM error here, as no references are kept, the application will keep running no matter how large you chose ITERATIONS in the above example. The memory consumption of your process (visible in top (RES/RSS) or process explorer) grows unless the application gets to the wait command. In the setup above, it will allocate around 150 MB in memory.

    If you want the application to play safe, close the input stream right where it's created:

    MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
    

    and your process will not exceed 35 MB, independent of the iteration count.

    Quite simple and surprising.

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

    As a lot of people have suggested, Resource Leaks are fairly easy to cause - like the JDBC examples. Actual Memory leaks are a bit harder - especially if you aren't relying on broken bits of the JVM to do it for you...

    The ideas of creating objects that have a very large footprint and then not being able to access them aren't real memory leaks either. If nothing can access it then it will be garbage collected, and if something can access it then it's not a leak...

    One way that used to work though - and I don't know if it still does - is to have a three-deep circular chain. As in Object A has a reference to Object B, Object B has a reference to Object C and Object C has a reference to Object A. The GC was clever enough to know that a two deep chain - as in A <--> B - can safely be collected if A and B aren't accessible by anything else, but couldn't handle the three-way chain...

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