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

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

什么样的例子?


当前回答

不终止的线程(比如在其运行方法中无限期休眠)。即使我们丢失了对它的引用,它也不会被垃圾收集。您可以添加字段以使线程对象变大。

目前排名靠前的答案列出了更多的技巧,但这些似乎是多余的。

其他回答

我认为,一个有效的例子可能是在线程集中的环境中使用ThreadLocal变量。

例如,使用Servlet中的ThreadLocal变量与其他web组件通信,让容器创建线程,并在池中维护空闲线程。ThreadLocal变量如果没有正确清理,将一直存在,直到同一个web组件覆盖它们的值。

当然,一旦确定,问题很容易解决。

关于如何在Java中创建内存泄漏,有很多答案,但请注意采访中提出的问题。

“如何使用Java创建内存泄漏?”是一个开放式问题,其目的是评估开发人员的经验程度。

如果我问你“你有解决Java内存泄漏的经验吗?”,你的答案很简单:“是”。然后,我会继续说“你能给我举个例子来解决内存泄漏问题吗?”,你会给我一两个例子。

然而,当面试官问“如何用Java创建内存泄漏?”时,预期答案应该是以下几行:

我遇到了内存泄漏。。。(说什么时候)[这显示了我的经验]导致它的代码是。。。(解释代码)[你自己修的]我应用的修复基于。。。(解释修复)[这让我有机会询问修复的细节]我做的测试是。。。[让我有机会询问其他测试方法]我是这样记录的。。。[额外加分。如果你记录下来,那就好了]因此,有理由认为,如果我们按照相反的顺序执行,也就是说,得到我修复的代码,然后删除我的修复,我们就会出现内存泄漏。

当开发人员未能遵循这一思路时,我试图引导他/她问“你能给我一个Java如何泄漏内存的例子吗?”,然后问“你曾经修复过Java中的内存泄漏吗?”

请注意,我并不是在询问如何在Java中泄漏内存的示例。那太傻了。谁会对一个能够有效编写泄漏内存的代码的开发人员感兴趣?

Java1.6中的String.substring方法会造成内存泄漏。这篇博文解释了这一点:

SubString方法在Java中的工作原理-JDK1.7中修复了内存泄漏

在Java中,“内存泄漏”主要是因为您使用了太多内存,这与在C中不同,在C中,您不再使用内存,而是忘记返回(释放)内存。当面试官询问Java内存泄漏时,他们询问的是JVM内存使用情况,但似乎一直在增加,他们认为定期重新启动JVM是最好的解决方案(除非面试官非常精通技术)。

所以,回答这个问题,就像他们问JVM内存使用量随时间增长的原因一样。好的答案是在HttpSessions中存储太多数据,超时时间过长,或者内存缓存(singleton)实现不佳,从不刷新旧条目。另一个可能的答案是拥有大量JSP或动态生成的类。类被加载到一个名为PermGen的内存区域,该区域通常很小,大多数JVM不实现类卸载。

这是一个简单/险恶的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实例被丢弃之后也是如此。