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

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

什么样的例子?


当前回答

如果您不了解JDBC,下面是一个毫无意义的示例。或者至少是JDBC希望开发人员在丢弃Connection、Statement和ResultSet实例或丢失对它们的引用之前关闭它们,而不是依赖于实现finalize方法。

void doWork() {
    try {
        Connection conn = ConnectionFactory.getConnection();
        PreparedStatement stmt = conn.preparedStatement("some query");
        // executes a valid query
        ResultSet rs = stmt.executeQuery();
        while(rs.hasNext()) {
            // ... process the result set
        }
    } catch(SQLException sqlEx) {
        log(sqlEx);
    }
}

上面的问题是Connection对象没有关闭,因此物理Connection将保持打开状态,直到垃圾回收器返回并发现它不可访问为止。GC将调用finalize方法,但有些JDBC驱动程序没有实现finalize,至少与Connection.close的实现方式不同。由此产生的行为是,尽管JVM将由于收集不可访问的对象而回收内存,但与Connection对象关联的资源(包括内存)可能不会被回收。

因此,Connection的最终方法并不能清除所有内容。人们可能会发现,到数据库服务器的物理连接将持续几个垃圾收集周期,直到数据库服务器最终发现该连接不活动(如果存在),应该关闭。

即使JDBC驱动程序实现了finalize,编译器也可以在finalize期间抛出异常。由此产生的行为是,与现在“休眠”对象关联的任何内存都不会被编译器回收,因为finalize保证只被调用一次。

上述在对象完成过程中遇到异常的场景与另一种可能导致内存泄漏的场景有关——对象复活。对象复活通常是通过创建一个从另一个对象最终确定的对象的强引用来实现的。当对象复活被误用时,它将与其他内存泄漏源一起导致内存泄漏。

还有很多例子你可以想象出来

管理列表实例,其中您只添加到列表中,而不从列表中删除(尽管您应该删除不再需要的元素),或者打开套接字或文件,但不再需要时不关闭它们(类似于上面涉及Connection类的示例)。在关闭Java EE应用程序时不卸载Singleton。加载单例类的Classloader将保留对该类的引用,因此JVM永远不会收集单例实例。当部署应用程序的新实例时,通常会创建一个新的类加载器,而由于单例,前一个类加载器将继续存在。

其他回答

如果最大堆大小为X.Y1….Yn实例数

因此,总内存=每个实例的实例数X字节。如果X1……Xn是每个实例的字节数,则总内存(M)=Y1*X1++Yn*Xn。因此,如果M>X,它将超过堆空间。

以下可能是代码中的问题

使用更多实例变量,然后使用局部变量。每次都创建实例,而不是共享对象。未按需创建对象。操作完成后使对象引用为空。再次,在程序中需要时重新创建。

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

也许通过JNI使用外部本机代码?

使用纯Java,这几乎是不可能的。

但这是一种“标准”类型的内存泄漏,即您无法再访问内存,但它仍然属于应用程序。相反,您可以保留对未使用对象的引用,或者打开流而不关闭它们。

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

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

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

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

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

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

我觉得有趣的是,没有人使用内部类示例。如果您有内部类;它固有地维护对包含类的引用。当然,从技术上讲,这不是内存泄漏,因为Java最终会清理掉它;但这会导致类停留的时间比预期的长。

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

现在,如果您调用Example1并得到一个Example2丢弃Example1,那么您本质上仍然有一个到Example1对象的链接。

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

我还听到一个传言,如果你有一个变量存在的时间超过了一个特定的时间;Java假设它将永远存在,并且如果代码中无法访问它,它实际上永远不会尝试清理它。但这完全未经证实。