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

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

什么样的例子?


当前回答


import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class Main {
    public static void main(String args[]) {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            ((Unsafe) f.get(null)).allocateMemory(2000000000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

其他回答

这里的大多数例子都“过于复杂”。它们是边缘案例。在这些例子中,程序员犯了一个错误(比如不要重新定义equals/hashcode),或者被JVM/JAVA的一个极端情况(用静态加载类…)所咬。我认为这不是面试官想要的例子,甚至不是最常见的例子。

但内存泄漏的情况确实更简单。垃圾收集器只释放不再引用的内容。我们作为Java开发人员并不关心内存。我们在需要时分配它,并让它自动释放。好的

但任何长寿命的应用程序都倾向于共享状态。它可以是任何东西,静态的,单态的。。。通常,非平凡的应用程序倾向于生成复杂的对象图。只是忘记将引用设置为null,或者更经常地忘记从集合中删除一个对象,就足以造成内存泄漏。

当然,如果处理不当,所有类型的侦听器(如UI侦听器)、缓存或任何长期共享状态都会产生内存泄漏。应该理解的是,这不是Java角落的情况,也不是垃圾收集器的问题。这是一个设计问题。我们设计为向长寿命对象添加侦听器,但在不再需要时不删除侦听器。我们缓存对象,但我们没有从缓存中删除它们的策略。

我们可能有一个复杂的图来存储计算所需的先前状态。但前一状态本身与前一状态相关联,依此类推。

就像我们必须关闭SQL连接或文件一样。我们需要设置对null的正确引用,并从集合中删除元素。我们应该有适当的缓存策略(最大内存大小、元素数量或计时器)。所有允许通知侦听器的对象必须同时提供addListener和removeListener方法。当这些通知器不再使用时,它们必须清除侦听器列表。

内存泄漏确实是可能的,而且完全可以预测。无需特殊的语言功能或角盒。内存泄漏要么是某些东西可能丢失的指示,甚至是设计问题。

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

我想就如何使用JVM中可用的工具监视应用程序的内存泄漏提供建议。它没有显示如何生成内存泄漏,但解释了如何使用最少的可用工具检测内存泄漏。

您需要首先监视Java内存消耗。

最简单的方法是使用JVM附带的jstat实用程序:

jstat -gcutil <process_id> <timeout>

它将报告每一代(年轻、老年和老年)的内存消耗和垃圾收集时间(年轻和完整)。

一旦您发现一个完整的垃圾收集执行得太频繁并且花费了太多时间,您就可以假设应用程序正在泄漏内存。

然后需要使用jmap实用程序创建内存转储:

jmap -dump:live,format=b,file=heap.bin <process_id>

然后需要使用内存分析器(例如Eclipse memory Analyzer(MAT))分析heap.bin文件。

MAT将分析内存并向您提供有关内存泄漏的可疑信息。

我在javax.swing.JPopupMenu中遇到了非常真实的内存泄漏。

我有一个GUI应用程序,它显示多个选项卡式文档。关闭文档后,如果在选项卡上的任何组件上使用了右键单击上下文菜单,它就会在内存中停留。这些菜单在选项卡之间共享,结果是,在调用popupMenu.show(component invoker,int x,int y)后,组件会作为菜单的“调用程序”静静地存在,直到下一次更改或被setInvoker(null)清除。间接地,调用者引用持久化了整个文档以及与之相关的所有内容。

值得注意的是,菜单只能以这种方式保存对旧组件的一个引用,因此这种内存泄漏不会在没有绑定的情况下增长。

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