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

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

什么样的例子?


当前回答

重叠侦听器是内存泄漏的一个很好的例子:对象被添加为侦听器。当不再需要对象时,对象的所有引用都为空。然而,忘记从侦听器列表中删除对象会使对象保持活动状态,甚至对事件做出响应,从而浪费内存和CPU。看见http://www.drdobbs.com/jvm/java-qa/184404011

其他回答

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

下面将有一个不明显的Java泄漏案例,除了被遗忘的侦听器、静态引用、哈希图中的伪/可修改键,或者只是线程被卡住而没有机会结束其生命周期的标准案例之外。

File.deleteOnExit()-总是泄漏字符串,如果字符串是子字符串,则泄漏更严重(底层的char[]也泄漏)-在Java 7中,子字符串也会复制char[],因此后者不适用@丹尼尔,不过不需要投票。

我将集中讨论线程,以展示非托管线程的危险性,甚至不希望触及摆动。

Runtime.addShutdownHook,不删除。。。然后,即使使用removeShutdownHook,由于ThreadGroup类中关于未启动线程的错误,它也可能无法被收集,从而有效地泄漏了ThreadGroup。JGroup在GossipRouter中有漏洞。创建一个线程,但不是启动它,它属于与上面相同的类别。创建线程继承ContextClassLoader和AccessControlContext,加上ThreadGroup和任何InheritedThreadLocal,所有这些引用都是潜在的泄漏,以及类加载器加载的所有类和所有静态引用,以及ja-ja。这种效果在整个j.u.c.Executor框架中尤其明显,该框架具有超简单的ThreadFactory接口,但大多数开发人员对潜在的危险一无所知。此外,许多库确实会根据请求启动线程(太多行业流行的库)。ThreadLocal缓存;这些在很多情况下都是邪恶的。我相信每个人都看到过很多基于ThreadLocal的简单缓存,但坏消息是:如果线程在上下文ClassLoader的生命周期中继续运行超过预期,这是一个非常好的小泄漏。除非确实需要,否则不要使用ThreadLocal缓存。当ThreadGroup本身没有线程,但仍保留子ThreadGroups时,调用ThreadGroup.destroy()。一个严重的泄漏,将阻止ThreadGroup从其父级中删除,但所有子级都无法枚举。使用WeakHashMap和值(in)直接引用键。如果没有堆转储,这很难找到。这适用于可能将硬引用保留回受保护对象的所有扩展弱/软引用。将java.net.URL与HTTP(S)协议一起使用,并从(!)加载资源。这一个是特殊的,KeepAliveCache在系统ThreadGroup中创建了一个新线程,该线程泄漏了当前线程的上下文类加载器。当不存在活动线程时,线程会在第一个请求时创建,因此您可能会幸运,或者只是泄漏。泄漏在Java7中已经修复,创建线程的代码正确地删除了上下文类加载器。创建类似线程的情况很少(如ImageFetcher,也已修复)。使用充气器InputStream在构造函数(例如PNGImageDecoder)中传递新的java.util.zip充气器(),而不调用充气器的end()。好吧,如果你只传递一个新的构造函数,就没有机会。。。是的,如果将其作为构造函数参数手动传递,则对流调用close()不会关闭充气机。这不是真正的泄漏,因为它将由终结器释放。。。当它认为有必要时。直到那一刻,它会严重消耗本地内存,导致Linux oom_killer肆无忌惮地终止进程。主要的问题是,在Java中完成是非常不可靠的,G1使其更糟,直到7.0.2。故事的寓意:尽快释放本土资源;终结器太差了。与java.util.zip.Deflater的情况相同。这一情况更糟,因为Deflater在java中需要内存,即总是使用15位(最大值)和8个内存级别(最大值为9)来分配数百KB的本地内存。幸运的是,Deflater没有被广泛使用,据我所知,JDK没有任何误用。如果手动创建放气器或充气器,请始终调用end()。最后两种方法中最棒的一点是:您无法通过常规的分析工具找到它们。

(我可以根据要求再添加一些我遇到的时间浪费者。)

祝你好运,保持安全;泄漏是邪恶的!

就像这样!

public static void main(String[] args) {
    List<Object> objects = new ArrayList<>();
    while(true) {
        objects.add(new Object());
    }
}

答案完全取决于面试官认为他们在问什么。

在实践中是否可能造成Java泄漏?当然是这样,其他答案中有很多例子。

但有很多元问题可能被问到了?

理论上“完美”的Java实现是否容易泄漏?候选人是否理解理论与现实之间的区别?应聘者是否了解垃圾收集的工作原理?或者垃圾收集在理想情况下应该如何工作?他们知道他们可以通过本地接口调用其他语言吗?他们知道用其他语言泄露内存吗?应聘者是否知道什么是内存管理,以及Java的幕后情况?

我把你的元问题理解为“在这种面试情况下我可以用什么答案”。因此,我将重点关注面试技巧,而不是Java。我相信,你更可能重复在面试中不知道问题答案的情况,而不是你需要知道如何使Java泄漏。所以,希望这会有所帮助。

你可以培养的面试最重要的技能之一是学会积极倾听问题,并与面试官合作以提取他们的意图。这不仅可以让你以他们想要的方式回答他们的问题,还表明你有一些重要的沟通技巧。当要在许多同样有才华的开发人员之间做出选择时,我会雇佣一个在他们每次回应之前都能倾听、思考和理解的人。

您可以通过在类的finalize方法中创建类的新实例来创建移动内存泄漏。如果终结器创建多个实例,则会获得加分。下面是一个简单的程序,它可以在几秒钟到几分钟内泄漏整个堆,具体取决于堆的大小:

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}