我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
从finalize方法引发未处理的异常。
其他回答
Java1.6中的String.substring方法会造成内存泄漏。这篇博文解释了这一点:
SubString方法在Java中的工作原理-JDK1.7中修复了内存泄漏
正如许多人所建议的那样,资源泄漏很容易造成,就像JDBC示例一样。实际的内存泄漏有点困难——尤其是如果您不依赖JVM中的碎片来为您进行泄漏。。。
创建占地面积非常大的对象,然后无法访问这些对象的想法也不是真正的内存泄漏。如果没有东西可以访问它,那么它将被垃圾收集,如果有东西可以访问,那么它就不是泄漏。。。
然而,一种曾经有效的方法——我不知道它是否仍然有效——是有一条三深的环形链。正如在对象A中有对对象B的引用,对象B有对对象C的引用,而对象C有对对象A的引用。GC足够聪明,知道如果A和B不能被任何其他对象访问,但不能处理三方链,则可以安全地收集两个深链(如在A<-->B中)。。。
每个人都会忘记本机代码路径。以下是泄漏的简单公式:
声明本机方法。在本机方法中,调用malloc。不要打免费电话。调用本机方法。
记住,本机代码中的内存分配来自JVM堆。
我最近遇到了一种更微妙的资源泄漏。我们通过类加载器的getResourceAsStream打开资源,但碰巧输入流句柄没有关闭。
嗯,你可能会说,真是个白痴。
嗯,有趣的是:通过这种方式,您可以泄漏底层进程的堆内存,而不是JVM的堆内存。
您只需要一个jar文件,其中包含一个将从Java代码中引用的文件。jar文件越大,分配内存的速度越快。
您可以使用以下类轻松创建这样的jar:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class BigJarCreator {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
zos.putNextEntry(new ZipEntry("resource.txt"));
zos.write("not too much in here".getBytes());
zos.closeEntry();
zos.putNextEntry(new ZipEntry("largeFile.out"));
for (int i=0 ; i<10000000 ; i++) {
zos.write((int) (Math.round(Math.random()*100)+20));
}
zos.closeEntry();
zos.close();
}
}
只需粘贴到名为BigJarCreator.java的文件中,从命令行编译并运行它:
javac BigJarCreator.java
java -cp . BigJarCreator
等等:您在当前工作目录中找到一个jar存档,其中包含两个文件。
让我们创建第二个类:
public class MemLeak {
public static void main(String[] args) throws InterruptedException {
int ITERATIONS=100000;
for (int i=0 ; i<ITERATIONS ; i++) {
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
}
System.out.println("finished creation of streams, now waiting to be killed");
Thread.sleep(Long.MAX_VALUE);
}
}
这个类基本上什么都不做,只创建未引用的InputStream对象。这些对象将立即被垃圾收集,因此不会影响堆大小。对于我们的示例来说,从jar文件加载现有资源很重要,这里的大小很重要!
如果您有疑问,请尝试编译并启动上面的类,但确保选择了合适的堆大小(2MB):
javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak
在这里您不会遇到OOM错误,因为没有保留引用,所以无论您在上面的示例中选择了多大的ITERATIONS,应用程序都将继续运行。除非应用程序执行wait命令,否则进程的内存消耗(在顶部(RES/RSS)或进程资源管理器中可见)会增加。在上面的设置中,它将分配大约150 MB的内存。
如果希望应用程序安全运行,请在创建输入流的位置关闭输入流:
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
并且您的进程不会超过35MB,与迭代计数无关。
非常简单和令人惊讶。
这里的大多数例子都“过于复杂”。它们是边缘案例。在这些例子中,程序员犯了一个错误(比如不要重新定义equals/hashcode),或者被JVM/JAVA的一个极端情况(用静态加载类…)所咬。我认为这不是面试官想要的例子,甚至不是最常见的例子。
但内存泄漏的情况确实更简单。垃圾收集器只释放不再引用的内容。我们作为Java开发人员并不关心内存。我们在需要时分配它,并让它自动释放。好的
但任何长寿命的应用程序都倾向于共享状态。它可以是任何东西,静态的,单态的。。。通常,非平凡的应用程序倾向于生成复杂的对象图。只是忘记将引用设置为null,或者更经常地忘记从集合中删除一个对象,就足以造成内存泄漏。
当然,如果处理不当,所有类型的侦听器(如UI侦听器)、缓存或任何长期共享状态都会产生内存泄漏。应该理解的是,这不是Java角落的情况,也不是垃圾收集器的问题。这是一个设计问题。我们设计为向长寿命对象添加侦听器,但在不再需要时不删除侦听器。我们缓存对象,但我们没有从缓存中删除它们的策略。
我们可能有一个复杂的图来存储计算所需的先前状态。但前一状态本身与前一状态相关联,依此类推。
就像我们必须关闭SQL连接或文件一样。我们需要设置对null的正确引用,并从集合中删除元素。我们应该有适当的缓存策略(最大内存大小、元素数量或计时器)。所有允许通知侦听器的对象必须同时提供addListener和removeListener方法。当这些通知器不再使用时,它们必须清除侦听器列表。
内存泄漏确实是可能的,而且完全可以预测。无需特殊的语言功能或角盒。内存泄漏要么是某些东西可能丢失的指示,甚至是设计问题。