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

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

什么样的例子?


当前回答

内存泄漏是一种资源泄漏,当计算机程序错误地管理内存分配,导致不再需要的内存无法释放时,就会发生这种情况=>维基百科定义

这是一种相对基于上下文的主题,你可以根据自己的喜好创建一个主题,只要未使用的引用永远不会被客户使用,但仍然存在。

第一个例子应该是一个自定义堆栈,而不取消有效Java第6项中过时的引用。

当然,只要你愿意,还有很多,但如果我们看看Java内置类,它可能是

子列表()

让我们检查一些超级愚蠢的代码来产生泄漏。

public class MemoryLeak {
    private static final int HUGE_SIZE = 10_000;

    public static void main(String... args) {
        letsLeakNow();
    }

    private static void letsLeakNow() {
        Map<Integer, Object> leakMap = new HashMap<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            leakMap.put(i * 2, getListWithRandomNumber());
        }
    }



    private static List<Integer> getListWithRandomNumber() {
        List<Integer> originalHugeIntList = new ArrayList<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            originalHugeIntList.add(new Random().nextInt());
        }
        return originalHugeIntList.subList(0, 1);
    }
}

实际上,还有另一个技巧,我们可以利用HashMap的查找过程,使用HashMap造成内存泄漏。实际上有两种类型:

hashCode()始终相同,但equals()不同;使用随机hashCode()和equals()始终为true;

Why?

hashCode()->bucket=>equals()来定位该对


我打算先提到substring(),然后再提到subList(),但这个问题似乎已经解决了,因为它的源代码在JDK8中。

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}

其他回答

我可以从这里复制我的答案:在Java中导致内存泄漏的最简单方法

“在计算机科学中,当计算机程序消耗内存但无法将其释放回操作系统时,就会发生内存泄漏。”(维基百科)

简单的答案是:你不能。Java执行自动内存管理,并将释放您不需要的资源。你无法阻止这种情况的发生。它将始终能够释放资源。在具有手动内存管理的程序中,这是不同的。可以使用malloc()在C中获得一些内存。要释放内存,您需要malloc返回的指针并对其调用free()。但是,如果您不再拥有指针(被覆盖或超过生存期),那么很遗憾,您无法释放此内存,因此会出现内存泄漏。

到目前为止,所有其他答案在我的定义中都不是真正的内存泄漏。他们的目标都是快速用毫无意义的东西填满记忆。但在任何时候,您仍然可以取消引用创建的对象,从而释放内存-->无泄漏。尽管我不得不承认,acconrad的答案非常接近,因为他的解决方案实际上是通过强制垃圾收集器进入一个无休止的循环来“崩溃”垃圾收集器)。

长时间的答案是:通过使用JNI为Java编写库,可以获得内存泄漏,JNI可以进行手动内存管理,从而产生内存泄漏。如果调用此库,Java进程将泄漏内存。或者,JVM中可能存在bug,从而导致JVM丢失内存。JVM中可能存在bug,甚至可能存在一些已知的bug,因为垃圾收集并不是那么简单,但它仍然是一个bug。根据设计,这是不可能的。您可能需要一些受此类错误影响的Java代码。很抱歉,我不知道,而且在下一个Java版本中,它可能不再是一个bug。

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

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

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

可能是潜在内存泄漏以及如何避免它的最简单示例之一,是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)?该引用可能会使一个巨大的对象保持活力。。。

我最近遇到了由log4j引起的内存泄漏情况。

Log4j有一种称为嵌套诊断上下文(NDC)的机制,它是一种区分不同来源的交织日志输出的工具。NDC工作的粒度是线程,因此它区分不同线程的日志输出。

为了存储线程特定的标记,log4j的NDC类使用一个Hashtable,该Hashtable由thread对象本身(而不是线程id)键控,因此直到NDC标记保留在内存中,挂在线程对象上的所有对象也保留在内存。在我们的web应用程序中,我们使用NDC标记带有请求id的登录,以将日志与单个请求区分开来。将NDC标记与线程关联的容器在返回请求响应时也会将其删除。在处理请求的过程中,产生了一个子线程,类似于以下代码:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

因此,NDC上下文与派生的内联线程相关联。这个NDC上下文的关键线程对象是一个内联线程,它挂着hugeList对象。因此,即使线程完成了它正在做的事情,对hugeList的引用也会被NDC上下文Hastable保持活动状态,从而导致内存泄漏。