开发者

Easiest way to cause a memory leak in Java [duplicate]

This question already has answers here: Closed 10 years ago.

Possible Duplicate: 开发者_StackOverflow

Creating a memory leak with Java

What's the easiest way to cause a Java memory leak?


You cannot really "leak memory" in Java unless you:

  • intern strings
  • generate classes
  • leak memory in the native code called by JNI
  • keep references to things that you do not want in some forgotten or obscure place.

I take it that you are interested in the last case. The common scenarios are:

  • listeners, especially done with inner classes
  • caches.

A nice example would be to:

  • build a Swing GUI that launches a potentially unlimited number of modal windows;
  • have the modal window do something like this during its initialization:
    StaticGuiHelper.getMainApplicationFrame().getOneOfTheButtons().addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent e){
    // do nothing...
    }
    })
    

The registered action does nothing, but it will cause the modal window to linger in memory forever, even after closing, causing a leak - since the listeners are never unregistered, and each anonymous inner class object holds a reference (invisible) to its outer object. What's more - any object referenced from the modal windows have a chance of leaking too.

This is why libraries such as EventBus use weak references by default.

Apart from listeners, other typical examples are caches, but I cannot think of a nice example.


First we have to agree on what a memory leak actually is.

Wikipedia used to describe a memory leak like this:

A memory leak, in computer science (or leakage, in this context), occurs when a computer program consumes memory but is unable to release it back to the operating system.

However this has changed multiple times and right now (02/2023) it says:

In computer science, a memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in a way that memory which is no longer needed is not released.

Depending on the context you need to specify what exactly you are looking for more precisely.

Unreachable dynamically allocated memory

First, let us have a quick look at an example from a language without automatic memory management: In C you can use malloc() in order to allocate some memory. This function returns a pointer to the allocated memory. You must call free() on exactly this pointer in order to release the memory back to the operating system. But what if the pointer is used in multiple places? Who is responsible for calling free()? If you release the memory too early, then some parts of your application that is still working with that memory is broken. If you do not release the memory, you have a leak. If all pointers to the memory allocated are lost (overwritten or lifetime exceeded), then your application will be unable to release the memory back to the operating system. This would fulfill the old definition that Wikipedia had for a memory leak in 2011. To avoid this, you need some kind of contract that defines who is responsible for freeing memory that was allocated. This requires documentation, which must be read, correctly understood and followed by possibly many people creating various opportunities for errors.

Automatic memory management (which Java has) frees you from this danger. In Java you can allocate memory using the keyword new, but there is no free in Java. new returns a "reference", which (in this context) behaves similarly to a pointer. When all references to allocated memory are lost (overwritten or lifetime exceeded) then this is detected automatically and the memory is returned to the operating system.

In Java this type of memory leak is only "available" in case of bugs in the garbage collector, JNI modules that leak memory or similar, but at least in theory you are safe.

Other programming errors

That withstanding it is of course both with and without automatic memory management possible to actively maintain unneeded references. Assume the following class:

class Demo {
    private static final LinkedList<Integer> history = new LinkedList<>(Collections.singleton(0));

    public static int plusPrevious(int value) {
        int result = history.getLast() + value;
        history.add(value);
        return result;
    }
}

Everytime someone calls plusPrevious the history-List grows. But why? Only one value is needed, not the full history. This class is holding on to memory which it does not need. This fulfills the current definition that Wikipedia has for a memory leak.

In this case the error is obvious. However in more complicated scenarios it might not be so easy to decide what is still "needed" and what is not.

At any rate, putting things in static variables is "good" start to get into trouble. If in the example above the history were not static then a user of that class might eventually release the reference to the instance of Demo and thus free the memory. However since it is static the history will hang around until the application as a whole terminates.


Here's a simple example

public class Finalizer {
    @Override
    protected void finalize() throws Throwable {
        while (true) {
            Thread.yield();
        }
    }

    public static void main(String[] args) {
        while (true) {
            for (int i = 0; i < 100000; i++) {
                Finalizer f = new Finalizer();
            }

            System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!");
        }
    }
}


Use:

public static List<byte[]> list = new ArrayList<byte[]>();

And then add (big) arrays without removing them. At some point you will run out of memory without suspecting it. (You can do this with any objects, but with big, full arrays you can run out of memory faster.)

In Java, if you dereference an object (it falls out of scope), it is garbage collected. So you have to hold a reference to it in order to have a memory problem.


  1. Create a collection of objects at class scope
  2. Periodically add new objects to the collection
  3. Do not drop the reference to the instance of the class that holds the collection

Because there is always a reference to the collection and the instance of the object that owns the collection, the garbage collector will never clean up that memory, thus causing a "leak" over time.


From what I've read in the most voted answer, you are most probably asking for a C-like memory leak. Well, since there's garbage collection, you can't allocate an object, lose all its references and get it still occupying memory - that would be a serious JVM bug.

On the other hand, you can happen to leak threads - which, of course, would cause this state, because you would have some thread running with its references to objects, and you may lose the thread's reference. You can still get the Thread reference through the API - see http://www.exampledepot.com/egs/java.lang/ListThreads.html


The following extremely contrived Box class will leak memory if used. Objects that are put into this class are eventually (after another call to put to be precise... provided the same object is not re-put into it.) inaccessible to the outside world. They cannot be dereferenced through this class, yet this class ensures they cannot be collected. This is a real leak. I know this is really contrived, but similar cases are possible to do by accident.

import java.util.ArrayList;
import java.util.Collection;
import java.util.Stack;

public class Box <E> {
    private final Collection<Box<?>> createdBoxes = new ArrayList<Box<?>>();
    private final Stack<E> stack = new Stack<E>();

    public Box () {
        createdBoxes.add(this);
    }

    public void put (E e) {
        stack.push(e);
    }

    public E get () {
        if (stack.isEmpty()) {
            return null;
        }
        return stack.peek();
    }
}


Try this simple class:

public class Memory {
    private Map<String, List<Object>> dontGarbageMe = new HashMap<String, List<Object>>();

    public Memory() {
        dontGarbageMe.put("map", new ArrayList<Object>());
    }

    public void useMemInMB(long size) {
        System.out.println("Before=" + getFreeMemInMB() + " MB");

        long before = getFreeMemInMB();
        while ((before  - getFreeMemInMB()) < size) {
            dontGarbageMe.get("map").add("aaaaaaaaaaaaaaaaaaaaaa");
        }

        dontGarbageMe.put("map", null);

        System.out.println("After=" + getFreeMemInMB() + " MB");
    }

    private long getFreeMemInMB() {
        return Runtime.getRuntime().freeMemory() / (1024 * 1024);
    }

    public static void main(String[] args) {
        Memory m = new Memory();
        m.useMemInMB(15);  // put here apropriate huge value
    }
}


It does seem that most of the answers are not C style memory leaks.

I thought I'd add an example of a library class with a bug that will give you an out-of-memory exception. Again, it is not a true memory leak, but it is an example of something running out of memory that you would not expect.

public class Scratch {
    public static void main(String[] args) throws Exception {
        long lastOut = System.currentTimeMillis();
        File file = new File("deleteme.txt");

        ObjectOutputStream out;
        try {
            out = new ObjectOutputStream(
                    new FileOutputStream("deleteme.txt"));

            while (true) {
                out.writeUnshared(new LittleObject());
                if ((System.currentTimeMillis() - lastOut) > 2000) {
                    lastOut = System.currentTimeMillis();
                    System.out.println("Size " + file.length());
                    // out.reset();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class LittleObject implements Serializable {
    int x = 0;
}

You will find the original code and bug description at JDK-4363937: ObjectOutputStream is creating a memory leak

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜