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

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

什么样的例子?


当前回答

我曾经有过一次关于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)需要大约两天的时间才能到达。。。

其他回答

我曾经有过一次关于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)需要大约两天的时间才能到达。。。

GUI代码中的一个常见示例是创建小部件/组件并向某个静态/应用程序范围的对象添加侦听器,然后在小部件被破坏时不删除侦听器。不仅会出现内存泄漏,而且性能也会受到影响,因为无论你听什么都会引发事件,所有的老听众都会被调用。

您可以使用sun.misc.Unsafe类进行内存泄漏。事实上,这个服务类用于不同的标准类(例如java.nio类)。不能直接创建此类的实例,但可以使用反射来获取实例。

代码不会在EclipseIDE中编译-使用javac命令编译代码(编译期间,您会收到警告)

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();
        }
    }
}

从finalize方法引发未处理的异常。

面试官可能在寻找一个循环引用,比如下面的代码(顺便说一下,这只会在使用引用计数的非常旧的JVM中泄漏内存,而现在情况已经不是这样了)。但这是一个非常模糊的问题,因此这是展示您对JVM内存管理理解的绝佳机会。

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

然后您可以解释,使用引用计数,上面的代码会泄漏内存。但大多数现代JVM不再使用引用计数。大多数都使用一个清理垃圾收集器,它实际上会收集这些内存。

接下来,您可能会解释创建一个具有底层本机资源的Object,如下所示:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

然后您可以解释这在技术上是内存泄漏,但实际上泄漏是由JVM中的本机代码分配底层本机资源造成的,而Java代码没有释放这些资源。

最后,对于现代JVM,您需要编写一些Java代码来分配JVM感知范围之外的本地资源。