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

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

什么样的例子?


当前回答

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

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

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

其他回答

我曾经有过一次关于PermGen和XML解析的“内存泄漏”。我们使用的XML解析器(我记不清是哪一个)对标记名执行String.intern(),以加快比较速度。我们的一位客户有一个好主意,不将数据值存储在XML属性或文本中,而是将其存储为标记名,因此我们有了这样一个文档:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

事实上,他们没有使用数字,而是使用更长的文本ID(约20个字符),这些ID是唯一的,每天的使用率为1000万至1000万。这使得每天有200 MB的垃圾,而这些垃圾再也不需要了,也永远不会被GCed(因为它在PermGen中)。我们将permagen设置为512MB,因此内存不足异常(OOME)需要大约两天的时间才能到达。。。

您可以尝试使用while循环使许多缓冲的读取器尝试同时打开同一个文件,条件决不为false。最上面的樱桃是这些永远不会关闭。

我认为还没有人说过这一点:你可以通过重写finalize()方法来复活一个对象,这样finalize)就可以在某个地方存储对它的引用。垃圾回收器只会在对象上调用一次,因此在此之后,对象将永远不会被销毁。

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

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

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

Java中有很多内存泄漏的好例子,我将在这个答案中提到其中两个。

示例1:

以下是《有效Java,第三版》(第7项:消除过时的对象引用)一书中的一个内存泄漏的好例子:

// Can you spot the "memory leak"?
public class Stack {
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private Object[] elements;
    private int size = 0;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        return elements[--size];
    }

    /*** Ensure space for at least one more element, roughly* doubling the capacity each time the array needs to grow.*/
    private void ensureCapacity() {
        if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

本书的这一段描述了为什么此实现会导致内存泄漏:

如果堆栈增长然后收缩即使程序使用堆栈没有对它们的更多引用。这是因为堆栈维护对这些对象的过时引用。一个过时的引用只是一个永远不会被取消引用的引用再一次在这种情况下元素数组已过时。活动部分包括索引小于大小的元素

以下是本书解决此内存泄漏的解决方案:

解决这类问题的方法很简单:null out引用一旦过时。在Stack类的情况下,对项目的引用一经弹出就过时从堆栈中删除。pop方法的修正版本如下所示:

public Object pop() {
    if (size == 0) throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // Eliminate obsolete reference
    return result;
}

但我们如何防止内存泄漏的发生?这是本书中一个很好的警告:

一般来说,每当类管理自己的内存时,程序员应该警惕内存泄漏。每当元素元素中包含的任何对象引用都应该为空。

示例2:

观察者模式也会导致内存泄漏。您可以在以下链接中阅读此模式:观察者模式。

这是观察者模式的一种实现:

class EventSource {
    public interface Observer {
        void update(String event);
    }

    private final List<Observer> observers = new ArrayList<>();

    private void notifyObservers(String event) {
        observers.forEach(observer -> observer.update(event)); //alternative lambda expression: observers.forEach(Observer::update);
    }

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void scanSystemIn() {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            notifyObservers(line);
        }
    }
}

在这个实现中,EventSource(在Observer设计模式中是可观察的)可以保存到Observer对象的链接,但这个链接从未从EventSource的Observer字段中删除。所以垃圾收集器永远不会收集它们。解决这一问题的一个解决方案是向客户提供另一种方法,当他们不再需要这些观察员时,将上述观察员从观察员字段中删除:

public void removeObserver(Observer observer) {
    observers.remove(observer);
}