我刚刚接受了一次采访,被要求用Java创建内存泄漏。

不用说,我觉得自己很傻,不知道如何开始创作。

什么样的例子?


当前回答

可能是潜在内存泄漏以及如何避免它的最简单示例之一,是ArrayList.remove(int)的实现:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

如果您是自己实现的,您是否想过清除不再使用的数组元素(elementData[-size]=null)?该引用可能会使一个巨大的对象保持活力。。。

其他回答

保存对象引用的静态字段(尤其是最终字段)

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

(未关闭)开放流(文件、网络等)

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

未封闭的连接

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

JVM垃圾收集器无法访问的区域,例如通过本机方法分配的内存。

在web应用程序中,某些对象存储在应用程序范围中,直到应用程序被显式停止或删除。

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

不正确或不适当的JVM选项,例如IBM JDK上的noclassgc选项,它阻止未使用的类垃圾收集

请参阅IBM JDK设置。

任何时候,只要您保留对不再需要的对象的引用,就会出现内存泄漏。请参阅处理Java程序中的内存泄漏,以了解内存泄漏如何在Java中表现出来以及您可以如何处理它。

这是一个简单/险恶的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);
    }
}

因为子字符串指的是原始字符串的内部表示,所以原始字符串会保留在内存中。因此,只要你有一个StringLeaker在玩,你的记忆中也有整个原始字符串,即使你可能认为你只是在保存一个字符串。

避免存储对原始字符串的不需要的引用的方法如下:

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

为了增加坏处,您还可以.intern()子字符串:

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

这样做将在内存中保留原始的长字符串和派生的子字符串,即使在StringLeaker实例被丢弃之后也是如此。

线程在终止之前不会被收集。它们是垃圾收集的根源。它们是少数几个不能简单地通过忘记它们或清除对它们的引用来回收的对象之一。

考虑:终止工作线程的基本模式是设置线程看到的一些条件变量。线程可以定期检查变量,并将其作为终止的信号。如果变量未声明为volatile,那么线程可能看不到对变量的更改,因此它不知道终止。或者想象一下,如果一些线程想要更新共享对象,但在试图锁定该对象时出现死锁。

如果您只有少数线程,这些错误可能会很明显,因为您的程序将停止正常工作。如果您有一个线程池,可以根据需要创建更多线程,那么过时/卡住的线程可能不会被注意到,并且会无限累积,从而导致内存泄漏。线程可能会在应用程序中使用其他数据,因此也会阻止收集它们直接引用的任何数据。

作为玩具示例:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

可以任意调用System.gc(),但传递给leakMe的对象永远不会死。

我在Java中看到的大多数内存泄漏都与进程不同步有关。

进程A通过TCP与B对话,并告诉进程B创建一些东西。B向资源发出一个ID,比如432423,A将其存储在一个对象中,并在与B对话时使用。在某些情况下,A中的对象会被垃圾收集回收(可能是由于错误),但A从不告诉B这一点(可能是另一个错误)。

现在A不再拥有它在B的RAM中创建的对象的ID,B也不知道A不再引用该对象。实际上,对象是泄漏的。