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

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

什么样的例子?


当前回答

理论上你不能。Java内存模型阻止了这一点。但是,因为必须实现Java,所以可以使用一些警告。这取决于您可以使用什么:

如果可以使用本机,则可以分配以后不会放弃的内存。如果这是不可用的,那么Java有一个不为人知的小秘密。您可以请求一个不由GC管理的直接访问数组,因此可以很容易地用于造成内存泄漏。这由DirectByteBuffer提供(http://download.oracle.com/javase/1.5.0/docs/api/java/nio/ByteBuffer.html#allocateDirect(int))。如果不能使用其中任何一个,仍然可以通过欺骗GC来造成内存泄漏。JVM是使用一代垃圾收集来实现的。这意味着垃圾堆被划分为三个区域:年轻人、成年人和老年人。对象创建时从年轻区域开始。随着它被越来越多地使用,它逐渐发展到成人到老年人。最有可能到达接骨木区域的对象不会被垃圾收集。您无法确定对象是否泄漏,如果您请求停止并清理GC,它可能会清理它,但在很长一段时间内,它会泄漏。更多信息请访问(http://java.sun.com/docs/hotspot/gc1.4.2/faq.html)此外,类对象不需要是GC’ed。也许有办法做到这一点。

其他回答

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

如果最大堆大小为X.Y1….Yn实例数

因此,总内存=每个实例的实例数X字节。如果X1……Xn是每个实例的字节数,则总内存(M)=Y1*X1++Yn*Xn。因此,如果M>X,它将超过堆空间。

以下可能是代码中的问题

使用更多实例变量,然后使用局部变量。每次都创建实例,而不是共享对象。未按需创建对象。操作完成后使对象引用为空。再次,在程序中需要时重新创建。

也许通过JNI使用外部本机代码?

使用纯Java,这几乎是不可能的。

但这是一种“标准”类型的内存泄漏,即您无法再访问内存,但它仍然属于应用程序。相反,您可以保留对未使用对象的引用,或者打开流而不关闭它们。

我觉得有趣的是,没有人使用内部类示例。如果您有内部类;它固有地维护对包含类的引用。当然,从技术上讲,这不是内存泄漏,因为Java最终会清理掉它;但这会导致类停留的时间比预期的长。

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

现在,如果您调用Example1并得到一个Example2丢弃Example1,那么您本质上仍然有一个到Example1对象的链接。

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

我还听到一个传言,如果你有一个变量存在的时间超过了一个特定的时间;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实例被丢弃之后也是如此。