Java中是否可能存在内存泄漏问题?

35

我经常被问到这个问题。如何回答才算得上是一个好方法呢?


1
这里有非常详细的资源:http://www.ibm.com/developerworks/library/j-leaks/ - Amokrane Chentir
这个问题已经被问了很多次,正如你所建议的那样,在这个论坛上已经有很多答案了。 - Peter Lawrey
2
好问题。从这些答案中学到了很多东西。 - fastcodejava
2
在https://dev59.com/pmw15IYBdhLWcg3wqNgA中有详细的回答。 - Edwin Dalorzo
可能是常见的Java内存/引用泄漏模式?的重复问题。 - vinS
12个回答

24
在Java中是否可能存在内存泄漏取决于你所说的是哪种类型的内存泄漏。在传统的C / C++中,当应用程序完成对对象的使用时没有释放或处理它,就会发生内存泄漏。其中的一个子问题是循环引用,因为应用程序难以知道何时释放/处理它,并且因此而忽略了它。相关问题是应用程序在已释放对象后仍使用该对象,或尝试两次释放它。 Java和其他完全受管理的语言大多不会出现这些问题,因为GC会负责释放不再可达的对象。但是,在某些情况下,Java中的GC将错过从程序员的角度来看应该进行垃圾收集的对象。当GC无法确定无法访问某个对象时,就会发生这种情况:
- 程序的逻辑/状态可能是执行路径不能发生的情况。开发人员可能认为这是显而易见的,但是GC不能确定并保持谨慎(因为它需要)。 - 程序员可能是错误的,而GC正在避免可能导致悬空引用的问题。
总之,您可能会遇到这样一种情况:不想要的对象无法被垃圾回收,并占用内存...这就是内存泄漏。

那么,问题在于Java应用程序或库可以通过本地代码分配离堆对象,这些对象需要手动管理。如果应用程序/库有错误或使用不当,则可能会出现本地内存泄漏。 (例如:Android位图内存泄漏 ...请注意,此问题已在较新版本的Android中得到解决。)


1 - 我提到了一些事情。一些托管语言允许您编写非托管代码,从而可以创建经典的存储泄漏。 一些其他托管语言(或更准确地说是语言实现)使用引用计数而不是适当的垃圾回收。 基于引用计数的存储管理器需要某些东西(即应用程序)来打破循环......否则将导致存储泄漏。


11

是的,即使你有垃圾回收机制,内存泄漏仍然可能发生。例如,您可能会保留一些需要手动关闭的资源,例如数据库结果集。


10

5

4

是的,从某种意义上说,您的Java应用程序可以随着时间推移累积内存,垃圾回收器无法释放该内存。

通过保留对不需要/不想要的对象的引用,它们永远不会超出范围,其内存也不会被回收。


3

是的,如果您不取消引用对象,它们将永远不会被垃圾回收,并且内存使用量将增加。但是由于Java的设计方式,这很难实现,而在其他一些语言中,有时很难避免这种情况。

编辑:阅读Amokrane的链接。它很好。


如果您没有引用对象,它可能被垃圾回收。如果您正在引用该对象(并且不在闭环中),那么它就不是“泄漏”。因此,事实上。 - T.J. Crowder
1
@T.J. Crowder:这是对“泄漏”的一种非常严格的解释,而且并不是很有用。假设某个类维护了各种对象的内部缓存,但是不断地将新实例添加到该缓存中而不是重复使用它们。内存使用量不断增加,堆被填满了永远不会再次使用的对象,这看起来确实像是一个“内存泄漏”问题。 - Anon.
@Anon:我大多数时候都是在开玩笑。但你的观点实际上是一个非常好的“答案”,我建议将其添加为一个答案。 - T.J. Crowder

3
短答案是:
一款能干的Java虚拟机不会有内存泄漏,但可能使用比所需更多的内存,因为并非所有未使用的对象都被垃圾回收。此外,Java应用程序本身可能持有对它们不再需要的对象的引用,这可能导致内存泄漏。

JVM 怎么可能出现内存泄漏,它们并不是拥有内存的那一方? - Pacerier
JVM会分配并使用内存。包括堆内存和非堆内存。 - Stephen C

3

是的,这是可能的。

在《Effective Java》中,有一个涉及使用数组实现堆栈的示例。如果您的弹出操作仅仅是将索引值减小,那么就可能会出现内存泄漏。为什么?因为您的数组仍然引用了已弹出的值,而您仍然引用了堆栈对象。因此,对于这个堆栈实现来说,正确的做法是使用显式的null赋值清除弹出数组索引处的值的引用。


2

是的,在某些情况下可以这样做,例如当程序错误地保留了一个不再使用的对象的引用,因此它没有被垃圾回收器清除。

一个例子就是忘记关闭已打开的流:

class MemoryLeak {

    private void startLeaking() throws IOException {
        StringBuilder input = new StringBuilder();
        URLConnection conn = new URL("www.example.com/file.txt").openConnection();

        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));        

        while (br.readLine() != null) {
            input.append(br.readLine());
        }
    }

    public static void main(String[] args) throws IOException {
        MemoryLeak ml = new MemoryLeak();
        ml.startLeaking();
    }
}

2

书籍《Effective Java》给出了两个关于“内存泄漏”的原因:

  • 一旦您将对象引用放入缓存并忘记它在那里。该引用在变得无关紧要之前会长时间保留在缓存中。解决方法是将缓存表示为WeakHashMap
  • 在一个API中,客户端注册回调并且没有明确地重新注册它们。解决方法是仅存储对它们的弱引用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接