如何在 Java 中创建内存泄漏?

How can I create a memory leak in Java?

提问人: 提问时间:6/25/2011 最后编辑:21 revs, 16 users 26%MatBanik 更新时间:9/20/2023 访问量:745435

问:

我刚刚接受了一次面试,我被要求用 Java 创建内存泄漏

不用说,我觉得自己很愚蠢,不知道如何开始创建一个。

举个例子是什么?

Java 内存泄漏

评论

48赞 Peter - Reinstate Monica 5/12/2021
具有讽刺意味的是,对于每个重要的 Java 程序来说,更难的问题是如何造成内存泄漏!
3赞 Galik 7/23/2021
继续向容器添加新对象,但忘记添加删除它们的代码,或者实现部分工作的代码,这些代码不会随着程序的进行而清理所有对象。
5赞 Thomas W 2/3/2022
Java 服务器系统中最常见的内存泄漏处于共享状态 -- 缓存和服务在请求之间共享。这里的许多答案似乎过于复杂,忽略了这个明显而常见的领域。一种相当常见的泄漏模式可能是具有请求范围键的应用程序范围的 Map(例如,某种手动滚动的缓存)。

答:

38赞 2 revs, 2 users 67%Rogach #1

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

对于纯 Java,这几乎是不可能的。

但这是关于“标准”类型的内存泄漏,当您无法再访问内存时,它仍然归应用程序所有。您可以改为保留对未使用对象的引用,或者打开流而不关闭它们。

评论

28赞 Joachim Sauer 6/25/2011
这取决于“内存泄漏”的定义。如果“保留但不再需要的内存”,那么在 Java 中很容易做到。如果是“已分配但代码根本无法访问的内存”,那么它会变得稍微困难一些。
504赞 3 revs, 3 users 87%Peter Lawrey #2

一个简单的方法是使用带有不正确(或不存在)或 的 HashSet,然后继续添加“重复项”。与其忽略重复项,不如继续增长,您将无法删除它们。hashCode()equals()

如果你想让这些坏键/元素徘徊,你可以使用一个静态字段,比如

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}

Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.

评论

89赞 Donal Fellows 6/26/2011
实际上,即使元素类获取 hashCode 并且等于错误,您也可以从 HashSet 中删除元素;只需获取集合的迭代器并使用其 Remove 方法,因为迭代器实际上对底层条目本身而不是元素进行操作。(请注意,未实现的 hashCode/equals 不足以触发泄漏;默认值实现简单的对象标识,因此您可以获取元素并正常删除它们。
2赞 corsiKa 6/26/2011
@Donal你最后的陈述是不正确的。你无法获取该元素,因为你没有对它的引用。对放置在地图中的密钥的唯一引用是地图本身所包含的密钥。只有你去了,你才能把它弄出来。你是对的,你可以使用迭代器来删除它,但你不能总是对所有数据结构都这样做。例如,如果你不把@meriton答案中的引用清空,那将永远丢失。您无权访问后备数组,迭代器将停止它。BadKey myKey = new BadKey("key"); map.put(key,"value");
1赞 Peter Lawrey 6/26/2011
当 equals/hashCode 不正确时删除元素的唯一方法是在使用不同的比较方法找到匹配项时使用 Iterator.remove()。
14赞 corsiKa 6/27/2011
@Donal我想说的是,我不同意你对内存泄漏的定义。我会认为(继续类比)您的迭代器删除技术是泄漏下的滴水盘;无论滴水盘如何,泄漏仍然存在。
110赞 user541686 7/2/2011
我同意,这不是内存“泄漏”,因为您可以删除对哈希集的引用并等待 GC 启动,然后 presto!记忆回到了过去。
21赞 3 revs, 2 users 79%duffymo #3

创建一个静态 Map,并不断添加对它的硬引用。这些永远不会被垃圾回收。

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}

评论

93赞 Falmarri 7/22/2011
这是怎么回事?它完全按照你的要求去做。如果这是泄漏,那么在任何地方创建和存储对象就是泄漏。
3赞 Kyle 7/18/2012
我同意@Falmarri。我没有看到那里有泄漏,你只是在创建对象。您当然可以使用另一个名为“removeFromCache”的方法“回收”刚刚分配的内存。泄漏是指无法回收内存。
5赞 duffymo 7/18/2012
我的观点是,那些不断创建对象的人,也许将它们放入缓存中,如果他们不小心,最终可能会出现 OOM 错误。
8赞 Falmarri 7/19/2012
@duffymo:但这并不是问题真正要问的。这与简单地用完所有内存无关。
5赞 Ernest Friedman-Hill 1/3/2019
反对这个答案的评论非常有趣,因为超过 2000 票的被接受的答案说的是完全相同的事情,尽管是以一种高度混淆的方式。ThreadLocals 并不神奇——它们只是 Map 中的条目,由 Thread 中的成员变量指向。
76赞 2 revs, 2 users 86%Bill the Lizard #4

每当您保留对不再需要的对象的引用时,都会发生内存泄漏。请参阅处理 Java 程序中的内存泄漏,了解内存泄漏在 Java 中的表现形式以及您可以采取的措施。

评论

0赞 ehabkost 7/22/2011
@31eee384让我们在聊天中继续讨论
149赞 7 revs, 5 users 55%Vineet Reynolds #5

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

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);
    }
}

上述问题是对象没有关闭,因此物理对象将保持打开状态,直到垃圾收集器出现并发现它无法访问。GC 将调用该方法,但有些 JDBC 驱动程序不会以与实现相同的方式实现。由此产生的行为是,虽然 JVM 将由于收集了无法访问的对象而回收内存,但与该对象关联的资源(包括内存)可能不会被回收。ConnectionConnectionfinalizefinalize,Connection.closeConnection

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

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

上述在对象终结过程中遇到异常的场景与另一个可能导致内存泄漏的场景 - 对象复活有关。对象复活通常是有意完成的,方法是从另一个对象创建对正在完成的对象的强引用。当对象复活被滥用时,它将导致内存泄漏与其他内存泄漏源相结合。

你可以想出更多的例子——比如

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

评论

120赞 Hardwareguy 7/22/2011
通常,在达到内存限制之前,您将达到最大打开连接限制。不要问我为什么知道......
2587赞 12 revs, 5 users 51%Daniel Pryden #6

这是在纯 Java 中创建真正的内存泄漏(运行代码无法访问但仍存储在内存中的对象)的好方法:

  1. 应用程序创建一个长时间运行的线程(或使用线程池更快地泄漏)。
  2. 线程通过(可选的自定义)加载类。ClassLoader
  3. 该类分配了一大块内存(例如),将对它的强引用存储在静态字段中,然后在 .分配额外的内存是可选的(泄漏类实例就足够了),但它会使泄漏工作得更快。new byte[1000000]ThreadLocal
  4. 应用程序将清除对自定义类或从中加载自定义类的所有引用。ClassLoader
  5. 重复。

由于在 Oracle 的 JDK 中实现的方式,这会产生内存泄漏:ThreadLocal

  • 每个都有一个私有字段,它实际上存储了线程本地值。ThreadthreadLocals
  • 此映射中的每个都是对对象的弱引用,因此在对该对象进行垃圾回收后,将从映射中删除其条目。ThreadLocalThreadLocal
  • 但是每个值都是一个强引用,因此当一个(直接或间接)指向作为其的对象时,只要线程存在,该对象就不会被垃圾回收,也不会从映射中删除。ThreadLocal

在此示例中,强引用链如下所示:

Thread对象→映射→示例类→示例类的实例→静态字段→对象。threadLocalsThreadLocalThreadLocal

(这在创建泄漏方面并没有真正发挥作用,它只会使泄漏变得更糟,因为这个额外的引用链:示例类→ →它加载的所有类。在许多 JVM 实现中,尤其是在 Java 7 之前,情况更糟,因为类和 s 被直接分配到 permgen 中,并且根本没有被垃圾回收。ClassLoaderClassLoaderClassLoader

这种模式的一个变体是,如果您经常重新部署碰巧使用在某种程度上指向自身的应用程序,那么应用程序容器(如 Tomcat)可能会像筛子一样泄漏内存。这可能是由于许多微妙的原因而发生的,并且通常很难调试和/或修复。ThreadLocal

更新:由于很多人一直在要求它,下面是一些示例代码,展示了这种行为的实际效果

评论

227赞 earcam 6/28/2011
+1 ClassLoader 泄漏是 JEE 世界中最常见的内存泄漏,通常是由转换数据的第三方库(BeanUtils、XML/JSON 编解码器)引起的。当 lib 加载到应用程序的根类加载器之外但包含对类的引用(例如,通过缓存)时,可能会发生这种情况。当您取消部署/重新部署应用程序时,JVM 无法对应用程序的类加载器(以及它加载的所有类)进行垃圾回收,因此重复部署时,应用程序服务器最终会失败。如果幸运的话,你得到了 ClassCastException 的线索,z.x.y.Abc 不能被强制转换为 z.x.y.Abc
68赞 Adrian M 7/8/2011
+1:Classloader 泄漏是一场噩梦。我花了几个星期试图弄清楚它们。可悲的是,正如@earcam所说,它们主要是由第三方库引起的,而且大多数分析器都无法检测到这些泄漏。在这个博客上有一个关于Classloader泄漏的很好和清晰的解释。blogs.oracle.com/fkieviet/entry/......
130赞 4 revs, 4 users 84%meriton #7

潜在内存泄漏以及如何避免它的最简单示例之一可能是 ArrayList.remove(int) 的实现:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

如果你自己实现它,你会想到清除不再使用的数组元素()吗?这个参考可能会让一个巨大的物体保持活力......elementData[--size] = null

评论

5赞 rds 7/23/2011
这里的内存泄漏在哪里?
34赞 meriton 7/23/2011
@maniek:我并不是说这段代码存在内存泄漏。我引用它是为了表明有时需要不明显的代码来避免意外的对象保留。
41赞 3 revs, 2 users 71%pauli #8

在 GUI 代码中,一个常见的例子是创建小部件/组件并将侦听器添加到某个静态/应用程序范围的对象,然后在小部件被销毁时不删除侦听器。你不仅会得到内存泄漏,而且还会受到性能的影响,因为当你正在听什么时,你的所有老听众也会被调用。

36赞 2 revs, 2 users 85%Ron #9

我曾经遇到过一次与 PermGen 和 XML 解析相关的很好的“内存泄漏”。 我们使用的XML解析器(我不记得是哪一个)对标签名称进行了String.intern(),以加快比较速度。 我们的一位客户提出了一个好主意,即不以 XML 属性或文本形式存储数据值,而是以标记名的形式存储数据值,因此我们有一个文档,如下所示:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

事实上,他们没有使用数字,而是使用更长的文本 ID(大约 20 个字符),这些 ID 是独一无二的,每天以 1000-1500 万的速度出现。这样一来,每天就会产生 200 MB 的垃圾,这再也不需要了,也永远不会被 GCed(因为它在 PermGen 中)。我们将 permgen 设置为 512 MB,因此内存不足异常 (OOME) 大约需要两天时间才能到达......

评论

4赞 Paŭlo Ebermann 7/3/2011
只是为了吹毛求疵您的示例代码:我认为数字(或以数字开头的字符串)不允许作为 XML 中的元素名称。
0赞 jmiserez 4/19/2017
请注意,对于 JDK 7+ 来说,这不再是正确的,其中字符串实习发生在堆上。有关详细文章,请参阅此文章: java-performance.info/string-intern-in-java-6-7-8
0赞 anubhs 6/3/2020
那么,我认为使用 StringBuffer 代替 String 会解决这个问题吗?不是吗?
298赞 7 revs, 3 users 78%bestsss #10

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

  • File.deleteOnExit()- 总是泄漏字符串,如果字符串是子字符串,泄漏就更糟了(底层的 char[] 也被泄露了) - 在 Java 7 中,substring 也复制了 char[],所以后者不适用;@Daniel,不过不需要投票。

我将专注于线程,以展示非托管线程的危险,甚至不希望触及摆动。

  • Runtime.addShutdownHook而不是删除...然后,即使使用 removeShutdownHook,由于 ThreadGroup 类中关于未启动线程的错误,它可能不会被收集,也会有效地泄漏 ThreadGroup。JGroup在GossipRouter中存在泄漏。

  • 创建但未启动 a 与上述类别相同。Thread

  • 创建线程会继承 and 、 和 any ,所有这些引用都是潜在的泄漏,以及类加载器加载的整个类和所有静态引用,以及 ja-ja。这种效果在整个 j.u.c.Executor 框架中尤为明显,该框架具有超级简单的界面,但大多数开发人员对潜伏的危险一无所知。此外,许多库确实会根据请求启动线程(太多行业流行的库)。ContextClassLoaderAccessControlContextThreadGroupInheritedThreadLocalThreadFactory

  • ThreadLocal缓存;在许多情况下,这些都是邪恶的。我相信每个人都看过很多基于 ThreadLocal 的简单缓存,坏消息是:如果线程继续运行超过上下文 ClassLoader 的预期寿命,这是一个纯粹的小泄漏。除非确实需要,否则不要使用 ThreadLocal 缓存。

  • 当 ThreadGroup 本身没有线程,但仍保留子 ThreadGroup 时调用。一个严重的泄漏,将阻止 ThreadGroup 从其父级中删除,但所有子级都变得不可枚举。ThreadGroup.destroy()

  • 使用 WeakHashMap 和值 (in) 直接引用键。如果没有堆转储,这是很难找到的。这适用于所有可能保留对受保护对象的硬引用的扩展。Weak/SoftReference

  • 与 HTTP(S) 协议一起使用并从 (!) 加载资源。这个很特别,它在系统 ThreadGroup 中创建一个新线程,该线程泄漏了当前线程的上下文类加载器。当不存在活动线程时,线程是在第一次请求时创建的,因此您可能会很幸运或只是泄漏。泄漏已经在 Java 7 中修复,并且创建线程的代码正确地删除了上下文类加载器。创建类似线程的情况很少(如 ImageFetcher也已修复)。java.net.URLKeepAliveCache

  • 使用传入构造函数(例如)而不调用充气器。好吧,如果你只传入构造函数,就没有机会......是的,如果手动将流作为构造函数参数传递,则调用流不会关闭 inflater。这不是真正的泄漏,因为它会由终结者发布......当它认为有必要时。直到那一刻,它才会严重消耗本机内存,以至于可能导致 Linux oom_killer 杀死该进程而不受惩罚。主要问题是 Java 中的最终确定非常不可靠,G1 使情况变得更糟,直到 7.0.2。故事的寓意:尽快释放原生资源;终结者实在是太差了。InflaterInputStreamnew java.util.zip.Inflater()PNGImageDecoderend()newclose()

  • 与 的情况相同。这要糟糕得多,因为 Deflater 在 Java 中内存不足,即总是使用 15 位(最大)和 8 个内存级别(最大 9 个)来分配数百 KB 的本机内存。幸运的是,JDK没有被广泛使用,据我所知,JDK不包含任何误用。如果手动创建 或 .最后两个最好的部分:您无法通过可用的普通分析工具找到它们。java.util.zip.DeflaterDeflaterend()DeflaterInflater

(我可以根据要求添加一些我遇到的更多浪费时间的人。

祝你好运,注意安全;泄漏是邪恶的!

评论

28赞 leonbloy 7/9/2011
Creating but not starting a Thread...哎呀,几个世纪前我被这个咬得很厉害!(Java 1.3)
0赞 bestsss 7/9/2011
@leonbloy,在更糟糕的是,当线程直接添加到线程组中时,不启动意味着非常严重的泄漏。它不仅增加了计数,而且可以防止线程组破坏(较小的邪恶,但仍然是泄漏)unstarted
0赞 Lawrence Dol 11/8/2017
谢谢!“当 ThreadGroup 本身没有线程时调用 ThreadGroup.destroy()...”是一个非常微妙的错误;我已经追了几个小时了,误入歧途,因为在我的控制 GUI 中枚举线程什么也没显示,但线程组和至少一个子组不会消失。
2赞 Lawrence Dol 11/10/2017
@bestsss : 我很好奇,既然它在 JVM 关闭时运行,你为什么要删除关闭钩子?
0赞 Lawrence Dol 9/12/2020
@user253751 :这是在内存泄漏的上下文中。在这种情况下,在使用关闭钩子后未能将其删除并不是泄漏,因为 JVM 正在关闭。但是,当然,如果您只是暂时需要在关闭时执行某些操作,那么不删除它并不断添加新的处理程序就会泄漏。
249赞 4 revs, 4 users 65%Nicolas Bousquet #11

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

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

但是,任何长期存在的应用程序都倾向于具有共享状态。它可以是任何东西,静态的、单例的......通常,非平凡的应用程序倾向于制作复杂的对象图形。只是忘记将引用设置为 null,或者更常见的是忘记从集合中删除一个对象,就足以导致内存泄漏。

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

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

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

内存泄漏确实是可能的,并且是完全可以预测的。不需要特殊的语言功能或极端情况。内存泄漏要么表明可能缺少某些东西,要么表明存在设计问题。

评论

48赞 ehabkost 7/22/2011
我觉得有趣的是,在其他答案中,人们正在寻找那些边缘情况和技巧,似乎完全没有抓住重点。他们可以只显示代码,这些代码保留了对永远不会再次使用的对象的无用引用,并且永远不会删除这些引用;有人可能会说这些情况不是“真正的”内存泄漏,因为周围仍然有对这些对象的引用,但如果程序不再使用这些引用,也从不删除它们,它完全等同于(并且与“真正的内存泄漏”一样糟糕)。
1348赞 13 revs, 12 users 57%Prashant Bhate #12

保存对象引用的静态字段 [尤其是最终字段]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

(未关闭)打开的流(文件、网络等)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStackTrace();
}

未闭合的连接

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStackTrace();
}

JVM 的垃圾回收器无法访问的区域,例如通过本机方法分配的内存。

在 Web 应用程序中,某些对象存储在应用程序范围内,直到显式停止或删除应用程序。

getServletContext().setAttribute("SOME_MAP", map);

不正确或不适当的 JVM 选项,例如 IBM JDK 上阻止未使用的类垃圾回收的选项noclassgc

请参阅 IBM JDK 设置

评论

217赞 Ian McLaird 7/13/2011
我不同意上下文和会话属性是“泄漏”。它们只是长期存在的变量。静态最终场或多或少只是一个常数。也许应该避免大常量,但我认为将其称为内存泄漏是不公平的。
100赞 bestsss 7/18/2011
未关闭的)打开的流(文件、网络等)不会真正泄漏,在定稿期间(将在下一个 GC 周期之后)将调度 close()(通常不会在终结器线程中调用,因为可能是一个阻塞操作)。不关闭是一种不好的做法,但不会导致泄漏。未关闭的 java.sql.Connection 是相同的。close()
41赞 mbauman 7/22/2011
在大多数理智的 JVM 中,String 类似乎对其哈希表内容只有弱引用。因此,它是正确收集的垃圾,而不是泄漏。(但 IANAJP)mindprod.com/jgloss/interned.html#GCintern
177赞 4 revs, 2 users 89%PlayTank #13

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

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

但是可能有多个元问题被问到?

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

我正在阅读您的元问题,即“在这种面试情况下,我可以使用什么答案”。因此,我将专注于面试技巧而不是 Java。我相信你更有可能在面试中重复不知道问题答案的情况,而不是处于需要知道如何让 Java 泄漏的地方。所以,希望这会有所帮助。

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

评论

28赞 DaveC 7/4/2011
每当我问这个问题时,我都在寻找一个非常简单的答案 - 继续增加队列,没有最终关闭的数据库等,而不是奇怪的类加载器/线程细节,这意味着他们了解 gc 可以为您做什么和不能为您做什么。我想这取决于你面试的工作。
48赞 4 revs, 3 users 70%yankee #14

我可以从这里复制我的答案: 在 Java 中导致内存泄漏的最简单方法

“在计算机科学中,当计算机程序消耗内存但无法将其释放回操作系统时,就会发生内存泄漏(或在这种情况下的泄漏)。(维基百科)

简单的答案是:你不能。Java 执行自动内存管理,并释放不需要的资源。你无法阻止这种情况的发生。它将始终能够释放资源。在手动内存管理的程序中,这是不同的。你可以使用 malloc() 在 C 语言中获得一些内存。要释放内存,您需要 malloc 返回的指针并对其调用 free()。但是,如果您不再拥有指针(覆盖或超过生存期),那么不幸的是,您无法释放此内存,因此存在内存泄漏。

到目前为止,所有其他答案在我的定义中都不是真正的内存泄漏。它们都旨在用毫无意义的东西快速填充记忆。但是在任何时候,您仍然可以取消引用您创建的对象,从而释放内存 - >不会泄漏。不过,我不得不承认,acconrad 的答案非常接近,因为他的解决方案实际上是通过强制垃圾收集器进入无限循环来“崩溃”垃圾收集器)。

长答案是:通过使用 JNI 为 Java 编写库,您可以获得内存泄漏,该库可以手动进行内存管理,因此存在内存泄漏。如果调用此库,则 Java 进程将泄漏内存。或者,您可以在 JVM 中出现错误,从而使 JVM 失去内存。JVM 中可能存在错误,甚至可能存在一些已知的错误,因为垃圾回收不是那么微不足道,但它仍然是一个错误。根据设计,这是不可能的。您可能要求提供一些受此类错误影响的 Java 代码。对不起,我不知道一个,无论如何,在下一个 Java 版本中它很可能不再是错误。

评论

17赞 Mason Wheeler 1/24/2014
这是对内存泄漏的极其有限(而且不是非常有用)的定义。对于实际目的,唯一有意义的定义是“内存泄漏是程序在不再需要它所保存的数据后继续保留分配的内存的任何情况。
0赞 Lii 11/21/2022
维基百科上该术语的定义已更改。现在是这样的:“在计算机科学中,内存泄漏是一种资源泄漏,当计算机程序错误地管理内存分配时,就会发生不再需要的内存不会被释放。这是几乎所有海报都使用的定义,但不是这个。
10赞 Martín Schonaker #15

我认为一个有效的例子可能是在线程池化的环境中使用 ThreadLocal 变量。

例如,在 Servlet 中使用 ThreadLocal 变量与其他 Web 组件进行通信,让容器创建线程并在池中维护空闲线程。如果未正确清理 ThreadLocal 变量,则将一直存在,直到同一 Web 组件可能覆盖其值。

当然,一旦确定,问题就可以轻松解决。

16赞 Ben #16

我认为还没有人说过这一点:您可以通过覆盖 finalize() 方法来复活一个对象,以便 finalize() 在某处存储对此的引用。垃圾回收器只会在对象上调用一次,因此在此之后,该对象将永远不会被销毁。

评论

10赞 bestsss 7/6/2011
这是不正确的。 不会被调用,但一旦没有更多引用,就会收集对象。垃圾回收器也没有被“调用”。finalize()
1赞 Tom 12/19/2012
这个答案具有误导性,JVM 只能调用该方法一次,但这并不意味着如果对象复活然后再次取消引用,则不能重新垃圾回收。如果方法中有资源关闭代码,则此代码将不会再次运行,这可能会导致内存泄漏。finalize()finalize()
41赞 2 revs, 2 users 59%Harald Wellmann #17

以在任何 servlet 容器(TomcatJettyGlassFish 等)中运行的任何 Web 应用程序为例。连续重新部署应用程序 10 或 20 次(只需触摸服务器自动部署目录中的 WAR 就足够了。

除非有人实际测试过这一点,否则在几次重新部署后,您很可能会收到 OutOfMemoryError,因为应用程序没有注意自行清理。通过此测试,您甚至可能会在服务器中发现错误。

问题是,容器的生存期比应用程序的生存期长。您必须确保容器可能具有的对应用程序的对象或类的所有引用都可以进行垃圾回收。

如果只有一个引用在取消部署 Web 应用程序后幸存下来,则无法对相应的类装入器以及 Web 应用程序的所有类进行垃圾回收。

由应用程序启动的线程、ThreadLocal 变量、日志记录追加器是导致类加载器泄漏的一些常见嫌疑人。

评论

2赞 Sven 5/22/2018
这不是因为内存泄漏,而是因为类装入器没有卸载前面的类集。因此,不建议在不重新启动服务器(不是物理机,而是应用程序服务器)的情况下重新部署应用程序服务器。我在 WebSphere 上看到了同样的问题。
0赞 Lii 11/21/2022
@Sven:这就是内存泄漏。在这种情况下,类装入器未卸载前一组类会导致内存泄漏。
12赞 2 revs, 2 users 89%Wesley Tarle #18

面试官可能一直在寻找循环参考解决方案:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

这是引用计数垃圾回收器的一个经典问题。然后,你会礼貌地解释一下,JVM 使用了一种更复杂的算法,没有这个限制。

评论

13赞 bestsss 7/6/2011
这是引用计数垃圾回收器的一个经典问题。甚至在 15 年前,Java 还不使用 ref counting。参考计数也比GC慢。
5赞 Esben Skov Pedersen 7/21/2011
不是内存泄漏。只是一个无限循环。
2赞 rds 7/23/2011
@Esben 在每次迭代中,前一个都没用,应该进行垃圾回收。在引用计数垃圾回收器中,对象不会被释放,因为该对象上有一个活动引用(它本身)。无限循环在这里是为了消除泄漏:当你运行程序时,内存将无限增加。first
0赞 nz_21 11/6/2018
@rds @Wesley Tarle 假设循环不是无限的。还会有内存泄漏吗?
23赞 Suroot #19

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

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

现在,如果您调用 Example1 并得到一个放弃 Example1 的 Example2,则您本质上仍将有一个指向 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 假设它将永远存在,如果无法在代码中访问它,实际上永远不会尝试清理它。但这是完全未经证实的。

评论

2赞 bestsss 7/9/2011
内部类很少成为问题。它们是一个简单的案例,非常容易被发现。谣言也只是谣言。
2赞 cHao 8/17/2013
这个“谣言”听起来像是有人对代际 GC 的工作原理读了一半。长期存在但现在无法访问的对象确实可以停留一段时间并占用空间,因为 JVM 将它们从年轻一代中提,因此它可以停止每次通过检查它们。通过设计,他们将逃避“清理我的 5000 个临时字符串”的琐碎传递。但它们不是不朽的。它们仍有资格进行收集,如果 VM 的 RAM 不足,它最终将运行完整的 GC 扫描并收回该内存。
10赞 2 revs, 2 users 73%Ben Xu #20

有许多不同的情况会泄漏内存。我遇到的一个,它暴露了一张不应该暴露在其他地方使用的地图。

public class ServiceFactory {

    private Map<String, Service> services;

    private static ServiceFactory singleton;

    private ServiceFactory() {
        services = new HashMap<String, Service>();
    }

    public static synchronized ServiceFactory getDefault() {

        if (singleton == null) {
            singleton = new ServiceFactory();
        }
        return singleton;
    }

    public void addService(String name, Service serv) {
        services.put(name, serv);
    }

    public void removeService(String name) {
        services.remove(name);
    }

    public Service getService(String name, Service serv) {
        return services.get(name);
    }

    // The problematic API, which exposes the map.
    // and user can do quite a lot of thing from this API.
    // for example, create service reference and forget to dispose or set it null
    // in all this is a dangerous API, and should not expose
    public Map<String, Service> getAllServices() {
        return services;
    }

}

// Resource class is a heavy class
class Service {

}
59赞 6 revs, 5 users 42%stemm #21

您可以使用 sun.misc.Unsafe 类进行内存泄漏。事实上,这个服务类用于不同的标准类(例如,在 java.nio 类中)。不能直接创建此类的实例,但可以使用反射来获取实例

代码无法在 Eclipse IDE 中编译 - 使用命令编译它(在编译过程中您会收到警告)javac

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

public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }
}
6赞 Gravity #22

一种可能性是为 ArrayList 创建一个包装器,该包装器仅提供一种方法:向 ArrayList 添加内容的方法。将 ArrayList 本身设为私有。现在,在全局范围内构造这些包装对象之一(作为类中的静态对象),并使用 final 关键字限定它(例如 )。所以现在引用不能改变。也就是说,将不起作用,也不能用于释放内存。但是,除了向其添加对象之外,也没有其他方法可以做任何事情。因此,您添加的任何对象都无法回收。public static final ArrayListWrapper wrapperClass = new ArrayListWrapper()wrapperClass = nullwrapperClasswrapperClass

9赞 Scott #23

我最近修复的一个例子是创建新的 GC 和 Image 对象,但忘记调用 dispose() 方法。

GC javadoc 代码段:

应用程序代码必须显式调用 GC.dispose() 方法以 在以下情况下释放每个实例管理的操作系统资源 不再需要这些实例。这一点尤为重要 在 Windows95 和 Windows98 上,操作系统有限 可用的设备上下文数。

图像 javadoc 片段:

应用程序代码必须显式调用 Image.dispose() 方法以 在以下情况下释放每个实例管理的操作系统资源 不再需要这些实例。

44赞 2 revsJon Chambers #24

这是一个简单/险恶的通过 http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

由于子字符串是指原始字符串的内部表示形式,因此原始字符串会保留在内存中。因此,只要你有一个 StringLeaker,你也会在内存中拥有整个原始字符串,即使你可能认为你只是在保留一个单字符字符串。

避免存储对原始字符串的不需要的引用的方法是执行如下操作:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

为了增加坏处,您还可以使用子字符串:.intern()

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

这样做会将原始长字符串和派生的子字符串保留在内存中,即使在丢弃 StringLeaker 实例后也是如此。

评论

21赞 anstarovoyt 3/22/2013
substring() 方法在 java7 中创建一个新的 String(这是一个新行为)
15赞 2 revs, 2 users 67%Graham #25

正如很多人所建议的那样,资源泄漏是相当容易导致的——就像 JDBC 的例子一样。实际的内存泄漏有点困难 - 特别是如果你不依赖 JVM 的损坏位来为你做这件事......

创建占用空间非常大的对象,然后无法访问它们的想法也不是真正的内存泄漏。如果没有东西可以访问它,那么它将被垃圾回收,如果有东西可以访问它,那么它就不是泄漏......

不过,过去有效的一种方法 - 我不知道它是否仍然有效 - 是拥有一条三深的圆形链。就像对象 A 引用对象 B、对象 B 引用对象 C 和对象 C 引用对象 A 一样。GC 足够聪明,知道如果 A 和 B 无法被其他任何东西访问,则可以安全地收集两条深链——如 A < > B,但无法处理三向链......

评论

7赞 assylias 6/21/2013
已经有一段时间没有了。现代 GC 知道如何处理循环引用。
23赞 2 revsPuneet #26

我最近遇到了由 log4j 引起的内存泄漏情况。

Log4j 具有这种称为嵌套诊断上下文 (NDC) 的机制,它是一种用于区分来自不同来源的交错日志输出的工具。NDC 工作的粒度是线程,因此它分别将日志输出与不同的线程区分开来。

为了存储特定于线程的标签,log4j 的 NDC 类使用一个 Hashtable,该表由 Thread 对象本身(而不是线程 ID)键控,因此直到 NDC 标签保留在内存中,线程对象挂起的所有对象也保留在内存中。在我们的 Web 应用程序中,我们使用 NDC 使用请求 ID 标记日志输出,以将日志与单个请求区分开来。将 NDC 标记与线程关联的容器也会在从请求返回响应时将其删除。在处理请求的过程中,生成子线程时,会出现此问题,类似于以下代码:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

因此,NDC 上下文与生成的内联线程相关联。作为此 NDC 上下文键的线程对象是内联线程,其上挂有 hugeList 对象。因此,即使在线程完成它正在执行的操作后,对 hugeList 的引用仍由 NDC 上下文 Hastable 保持活动状态,从而导致内存泄漏。

评论

0赞 TraderJoeChicago 9/21/2011
糟糕透了。您应该检查以下日志记录库,该库在日志记录到文件时分配零内存:mentalog.soliveirajr.com
0赞 sparc_spread 7/3/2014
+1 您是否知道 slf4j/logback(同一作者的后续产品)中的 MDC 是否存在类似问题?我即将对来源进行深入研究,但想先检查一下。无论哪种方式,感谢您发布此内容。
20赞 2 revs, 2 users 74%Paul Morie #27

大家总是忘记原生代码路由。以下是泄漏的简单公式:

  1. 声明本机方法。
  2. 在本机方法中,调用 .不要打电话给 .mallocfree
  3. 调用本机方法。

请记住,本机代码中的内存分配来自 JVM 堆。

8赞 2 revs, 2 users 80%Artur Ventura #28

理论上你不能。Java 内存模型阻止了它。但是,由于必须实现 Java,因此可以使用一些注意事项。这取决于您可以使用什么:

  • 如果可以使用本机,则可以分配以后不会放弃的内存。

  • 如果这不可用,那么关于Java有一个肮脏的小秘密,很少有人知道。您可以请求一个不受 GC 管理的直接访问阵列,因此可以很容易地用于造成内存泄漏。这是由 DirectByteBuffer (http://download.oracle.com/javase/1.5.0/docs/api/java/nio/ByteBuffer.html#allocateDirect(int)) 提供的。

  • 如果你不能使用其中任何一个,你仍然可以通过欺骗 GC 来造成内存泄漏。JVM 是使用分代垃圾回收实现的。这意味着堆被划分为区域:年轻人、成年人和老年人。创建对象时,它从年轻区域开始。随着它被越来越多地使用,它发展到成人到老年人。到达老年人区的物体很可能不会被垃圾收集。你不能确定一个物体是否泄漏,如果你要求停止并清洁GC,它可能会清洁它,但很长一段时间它都会泄漏。更多信息请见(http://java.sun.com/docs/hotspot/gc1.4.2/faq.html)

  • 此外,类对象不需要进行 GC 处理。可能有一种方法可以做到这一点。

评论

3赞 Boann 8/31/2013
当 DirectByteBuffer 被垃圾回收时,DirectByteBuffer 分配的本机内存将在终结器中释放。它当然不会泄漏。
17赞 sethobrien #29

可以通过在类的 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());
        }
    }
}
8赞 2 revs, 2 users 67%arnt #30

我在 Java 中看到的大多数内存泄漏都与进程不同步有关。

进程 A 通过 TCP 与 B 通信,并告诉进程 B 创建一些东西。B 向资源发出一个 ID,比如 432423,A 将其存储在一个对象中,并在与 B 通信时使用。在某个时候,A 中的对象被垃圾回收回收(可能是由于一个错误),但 A 从未告诉 B(可能是另一个错误)。

现在,A 不再拥有它在 B 的 RAM 中创建的对象的 ID,并且 B 不知道 A 不再引用该对象。实际上,对象已泄漏。

7赞 3 revs, 2 users 79%Peter #31

几点建议:

  • 在 servlet 容器中使用 commons-logging(也许有点挑衅)
  • 在 servlet 容器中启动线程,并且不从其 run 方法返回
  • 在 servlet 容器中加载动画 GIF 图像(这将启动动画线程)

通过重新部署应用程序;)可以“改善”上述效果

我最近偶然发现了这一点:

  • 调用“new java.util.zip.Inflater();”而不调用“Inflater.end()”

阅读 https://bugs.java.com/bugdatabase/view_bug?bug_id=5072161 和链接的问题,进行深入讨论。

评论

0赞 Peter 7/22/2011
在我看来,为了增加关于什么是内存泄漏的讨论,“故意”的想法很重要。当然,指向巨大数据结构的字段(静态或非静态)还不是内存泄漏。但是,如果指向的数据结构对你的逻辑没有用处,只是徘徊不前,污染你的堆,也许在GC放弃OOME之前一直在增长,那么我称之为“内存泄漏”。我不再可以使用泄漏的内存。就像以前我打电话给 malloc 而从未将其返回给操作系统一样 - 净化以救援。
0赞 bestsss 12/27/2011
充气器是一个很好的,虽然不是真正的泄漏(如果最终确定),但它很好地消耗了本机内存,并且很难诊断。你应该得到一票(但我自己不投票)。我在回答中添加了更多信息(之前没有发现您的)
0赞 Peter 2/2/2012
Finlizers 是个坏主意,而且不能保证它们会被召唤。Google for Cliff Click 以及他对此的评论......
0赞 bestsss 2/2/2012
所有 JVM(减去 7.0.2 之前的 JDK7)或多或少都以良好的方式调用终结器。终结器对于直接缓冲区的工作是绝对必要的(甚至更多的映射文件,尤其是在 Windows 上)。他们只是不可靠(除了我知道 Cliff Click 所说的内容)
5赞 2 revs, 2 users 60%Peter Mortensen #32

在 Java 中,“内存泄漏”主要是你使用了太多内存,这与在 C 中不同,在 C 中你不再使用内存,但忘记返回(释放)它。当面试官询问 Java 内存泄漏时,他们问的是 JVM 内存使用率似乎在不断上升,他们确定定期重新启动 JVM 是最好的解决方法(除非面试官非常精通技术)。

因此,回答这个问题就好像他们问是什么让 JVM 内存使用量随着时间的推移而增长一样。好的答案是在超时时间过长的 HttpSessions 中存储过多的数据,或者实现不当的内存中缓存(单例)从不刷新旧条目。另一个可能的答案是拥有大量 JSP 或动态生成的类。类被加载到称为 PermGen 的内存区域中,该区域通常很小,并且大多数 JVM 不实现类卸载。

15赞 Jay #33

我最近遇到了一种更微妙的资源泄漏。 我们通过类加载器的 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 文件加载现有资源很重要,大小在这里确实很重要!

如果您有疑问,请尝试编译并启动上面的类,但请确保选择一个合适的堆大小 (2 MB):

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

您不会在这里遇到 OOM 错误,因为没有保留引用,无论您在上面的示例中选择多大的 ITERATIONS,应用程序都会继续运行。 除非应用程序进入 wait 命令,否则进程(在顶部 (RES/RSS) 或进程资源管理器中可见)的内存消耗会增长。在上面的设置中,它将分配大约 150 MB 的内存。

如果希望应用程序安全运行,请在创建输入流的位置关闭输入流:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

您的进程不会超过 35 MB,与迭代计数无关。

相当简单和令人惊讶。

5赞 2 revs, 2 users 75%Peter Mortensen #34

Swing 通过对话非常容易。创建一个JDialog,显示它,用户关闭它,然后泄漏!

您必须调用或配置 .dispose()setDefaultCloseOperation(DISPOSE_ON_CLOSE)

7赞 2 revs, 2 users 68%Peter Mortensen #35

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

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

以下可能是代码中的问题

  1. 使用比本地变量更多的实例变量。
  2. 每次都创建实例,而不是池化对象。
  3. 不按需创建对象。
  4. 在操作完成后,使对象引用为 null。同样,在程序中需要时重新创建。
-7赞 2 revs, 2 users 69%stones333 #36

这是一个非常简单的 Java 程序,它将耗尽空间

public class OutOfMemory {

    public static void main(String[] arg) {

        List<Long> mem = new LinkedList<Long>();
        while (true) {
            mem.add(new Long(Long.MAX_VALUE));
        }
    }
}

评论

37赞 rds 1/17/2013
-1 这肯定会耗尽内存,因为要求是拥有无限量的内存。我不称其为内存泄漏。这只是一个愚蠢的程序。
3赞 kritzikratzi 4/2/2013
还有 -1,不是内存泄漏,只是分配太多了
4赞 user1050755 #37

如果不使用压缩垃圾回收器,则可能会由于堆碎片而导致某种内存泄漏。

4赞 Tarik #38

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

7赞 Basanth Roy #39

从 finalize 方法引发未经处理的异常。

13赞 4 revs, 3 users 73%Boann #40

线程在终止之前不会被收集。它们是垃圾回收的根源。它们是为数不多的不会通过忘记它们或清除对它们的引用而被回收的对象之一。

考虑一下:终止工作线程的基本模式是设置线程看到的一些条件变量。线程可以定期检查变量,并将其用作终止信号。如果未声明变量,则线程可能无法看到对变量的更改,因此它不知道终止。或者想象一下,如果某些线程想要更新共享对象,但在尝试锁定它时死锁。volatile

如果你只有少数几个线程,这些错误可能会很明显,因为你的程序将停止正常工作。如果您的线程池根据需要创建更多线程,则过时/卡住的线程可能不会被注意到,并且会无限累积,从而导致内存泄漏。线程可能会使用应用程序中的其他数据,因此也会阻止收集它们直接引用的任何内容。

以玩具为例:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

随心所欲地调用,但传递给的对象永远不会死。System.gc()leakMe

评论

1赞 Boann 9/27/2013
@Spidey 没有什么是“卡住”的。调用方法会立即返回,并且永远不会回收传递的对象。这恰恰是泄漏。
1赞 Spidey 9/27/2013
在程序的整个生命周期内,您将有一个线程“运行”(或休眠,其他任何)。这对我来说不算是泄漏。以及游泳池不算泄漏,即使您不完全使用它。
1赞 Boann 9/27/2013
@Spidey “在程序的整个生命周期中,你都会有一个[东西]。这对我来说不算是泄密。你听到了吗?
3赞 Boann 9/28/2013
@Spidey 如果您将进程知道的内存计算为未泄漏,那么这里的所有答案都是错误的,因为进程始终跟踪其虚拟地址空间中的哪些页面被映射。当进程终止时,操作系统会通过将页面放回可用页面堆栈来清理所有泄漏。为了更进一步,人们可以通过指出RAM芯片或磁盘交换空间中的任何物理位都没有物理错位或破坏来杀死任何有争议的泄漏,因此您可以关闭并再次打开计算机以清理任何泄漏。
1赞 Boann 9/28/2013
泄漏的实际定义是,它是丢失的内存,以至于我们不知道,因此无法执行仅回收它所需的程序;我们将不得不拆除并重建整个内存空间。像这样的流氓线程可能会通过死锁或狡猾的线程池实现自然产生。这些线程引用的对象,即使是间接引用的对象,现在也被阻止被收集,因此我们的内存在程序的生命周期内不会被自然回收或重用。我称之为问题;具体来说,这是内存泄漏。
9赞 2 revs, 2 users 75%Audrius Meskauskas #41

一个不会终止的线程(例如,在其 run 方法中无限期休眠)。即使我们丢失了对它的引用,它也不会被垃圾回收。您可以根据需要添加字段以使线程对象变大。

目前最热门的答案列出了更多关于这个问题的技巧,但这些似乎是多余的。

7赞 Alexandre Santos #42

关于如何在 Java 中创建内存泄漏有很多答案,但请注意面试中提出的问题。

“如何使用Java创建内存泄漏?”是一个开放式问题,其目的是评估开发人员的经验程度。

如果我问你“你有解决Java内存泄漏的经验吗?”,你的回答是简单的“是”。 然后,我必须接着说“你能给我举例说明你如何解决内存泄漏问题吗?”,你会给我一两个例子。

然而,当面试官问到“如何用Java造成内存泄漏?”时,预期的答案应该遵循以下几行:

  • 我遇到了内存泄漏......(说什么时候)[向我展示经验]
  • 导致它的代码是......(解释代码)[你自己修好了]
  • 我应用的修复程序基于...(解释修复)[这让我有机会询问有关修复的细节]
  • 我做的测试是......[让我有机会询问其他测试方法]
  • 我是这样记录的......[加分。如果你记录下来就好了]
  • 因此,可以合理地认为,如果我们以相反的顺序遵循此操作,即获取我修复的代码并删除我的修复程序,我们将发生内存泄漏。

当开发人员没有遵循这种思路时,我会试着引导他/她问“你能给我举个例子来说明Java是如何泄漏内存的吗?”,然后是“你有没有修复过Java中的任何内存泄漏?

请注意,我不是在要求一个关于如何在 Java 中泄漏内存的示例。那太傻了。谁会对能够有效地编写泄漏内存代码的开发人员感兴趣?

评论

0赞 Stefano Sanfilippo 6/22/2015
关于最后一句话,战胜邪恶的最好方法是充分了解它。如果要编写安全的 Web 应用程序,则应熟悉最常见的漏洞利用技术和漏洞,例如 SQL 注入或缓冲区溢出。同样,如果你想编写无泄漏的代码,你至少应该能够描述最常见的内存泄漏方式,比如 C/C++ 中的丢失指针。不过,在 Java 中肯定不那么简单。
7赞 2 revs, 2 users 64%Peter Mortensen #43

Java 1.6 中的 String.substring 方法会造成内存泄漏。这篇博文对此进行了解释:

SubString 方法在 Java 中的工作原理 - JDK 1.7 中修复的内存泄漏

27赞 3 revsbvdb #44

什么是内存泄漏:

  • 这是由错误糟糕的设计引起的。
  • 这是浪费记忆。
  • 随着时间的流逝,情况会变得更糟。
  • 垃圾回收器无法清理它。

典型示例:

对象缓存是搞砸事情的良好起点。

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

您的缓存会不断增长。很快,整个数据库就被占用在内存中。更好的设计使用 LRUMap(仅将最近使用的对象保留在缓存中)。

当然,你可以让事情变得更加复杂:

  • 使用 ThreadLocal 构造。
  • 添加更复杂的引用树
  • 由第三方库引起的泄漏。

经常发生的情况:

如果此 Info 对象具有对其他对象的引用,则这些对象又具有对其他对象的引用。在某种程度上,您也可以将其视为某种内存泄漏(由糟糕的设计引起)。

评论

0赞 bvdb 5/3/2021
稍微不相关的一点是:有一句流行的说法:编程中只有两件事是困难的:命名和缓存失效。
4赞 2 revs, 2 users 84%JaskeyLam #45

在具有自己生命周期的类中粗心地使用非静态内部类。

在 Java 中,非静态内部类和匿名类包含对其外部类的隐式引用。另一方面,静态内部类则不然

以下是 Android 中内存泄漏的常见示例,但这并不明显:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() { // Non-static inner class, holds the reference to the SampleActivity outer class
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for a long time.
    mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
      @Override
      public void run() {
     //....
      }
    }, SOME_TOME_TIME);

    // Go back to the previous Activity.
    finish();
  }}

这将防止对活动上下文进行垃圾回收。

评论

0赞 ban-geoengineering 6/24/2015
静电可以防止它泄漏内存吗?是否有任何(其他)方法可以防止泄露活动?另外,您将如何为匿名内部类解决相同的问题?mLeakyHandlermLeakyHandlerRunnable
1赞 JaskeyLam 6/25/2015
@ban地球工程 是的,让它静态,如果你需要参与外部活动,让处理程序持有对活动的 WeakReference,请检查 androiddesignpatterns.com/2013/01/...
-7赞 TV Trailers #46

Java 中没有内存泄漏这样的事情。内存泄漏是从 C 等人那里借来的一个短语。Java 在 GC 的帮助下在内部处理内存分配。有内存浪费(即留下搁浅的对象),但没有内存泄漏

7赞 2 revs, 2 users 80%user1529891 #47

Java 中的内存泄漏不是典型的 C/C++ 内存泄漏。

要了解 JVM 的工作原理,请阅读了解内存管理

基本上,重要的部分是:

标记和扫描模型

JRockit JVM 使用标记和清除垃圾回收模型 对整个堆执行垃圾回收。标记和扫描 垃圾回收由两个阶段组成,标记阶段和 扫描阶段。

在标记阶段,可从 Java 访问的所有对象 线程、本机句柄和其他根源被标记为活动,如 以及可从这些对象访问的对象等 第四。此过程识别并标记所有静止的对象 用过了,其余的可以算是垃圾。

在扫描阶段,遍历堆以查找 活动对象。这些差距记录在自由列表中并制作 可用于新对象分配。

JRockit JVM 使用标记和扫描的两个改进版本 型。一种主要是并发标记和扫描,另一种是 平行标记和扫描。您也可以混合使用这两种策略,运行 例如,主要是并发标记和并行扫描。

因此,要在 Java 中创建内存泄漏;最简单的方法是创建一个数据库连接,做一些工作,而不是它;然后生成新的数据库连接,同时保持在范围内。例如,这在循环中并不难做到。如果您有一个从队列中拉取并推送到数据库的工作线程,则很容易通过忘记连接或在不必要的时候打开连接等方式造成内存泄漏。Close()Close()

最终,您将通过忘记连接来消耗已分配给 JVM 的堆。这将导致 JVM 垃圾回收疯狂;最终导致错误。应该注意的是,该错误可能并不意味着存在内存泄漏;这可能只是意味着你没有足够的内存;例如,像 CassandraElasticsearch 这样的数据库可能会抛出该错误,因为它们没有足够的堆空间。Close()java.lang.OutOfMemoryError: Java heap space

值得注意的是,这适用于所有 GC 语言。以下是我作为 SRE 看到的一些示例:

  • 使用 Redis 作为队列的 Node.js;开发团队每 12 小时创建一次新连接,忘记关闭旧连接。最终节点是 OOMd,因为它消耗了所有内存。
  • 吧(我对此感到内疚);使用大型 JSON 文件解析,然后通过引用传递结果并使其保持打开状态。最终,这导致整个堆被我打开以解码 JSON 的意外引用所消耗。json.Unmarshal
29赞 3 revs, 3 users 66%deltamind106 #48

面试官可能正在寻找一个循环引用,就像下面的代码一样(顺便说一句,它只在使用引用计数的非常旧的 JVM 中泄漏内存,现在情况不再如此)。但这是一个非常模糊的问题,所以这是展示你对 JVM 内存管理的理解的绝佳机会。

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

然后你可以解释一下,使用引用计数时,上面的代码会泄漏内存。但是大多数现代 JVM 不再使用引用计数。大多数使用扫描垃圾收集器,它实际上会收集此内存。

接下来,您可以解释如何创建一个具有底层本机资源的 Object,如下所示:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

然后你可以解释这在技术上是内存泄漏,但实际上泄漏是由 JVM 中的原生代码分配底层原生资源引起的,而这些资源不是由 Java 代码释放的。

归根结底,对于现代 JVM,您需要编写一些 Java 代码,以在 JVM 感知的正常范围之外分配本机资源。

评论

1赞 Erwin Bolwidt 2/24/2022
没有 JVM 使用过引用计数。您可能会对 Microsoft 的 Java脚本的早期实现感到困惑。
-7赞 akhambir #49

就这样!

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

评论

12赞 Karan Khanna 4/19/2018
这不是内存泄漏的示例。你只是想用你的列表来消耗这里的整个内存。内存泄漏是指无法对过时的引用进行垃圾回收。
0赞 4 revs, 4 users 30%Ramesh R #50

JDK 1.7 之前的内存泄漏的实时示例:

假设您读取了一个包含 1000 行文本的文件,并将它们保存在 String 对象中:

String fileText = 1000 characters from file
fileText = fileText.subString(900, fileText.length());

在上面的代码中,我最初读取了 1000 个字符,然后执行子字符串以仅获取最后 100 个字符。现在应该只引用 100 个字符,所有其他字符都应该被垃圾回收,因为我丢失了引用,但在 JDK 1.7 之前,子字符串函数间接引用了最后 100 个字符的原始字符串,并防止整个字符串进行垃圾回收,整个 1000 个字符将存在于内存中,直到您丢失对子字符串的引用。fileText

您可以创建如上所述的内存泄漏示例。

评论

0赞 Karan Khanna 4/19/2018
我不这么认为。将创建并返回一个新的 String。这是来自 open-jdk 6 的 subString 函数返回代码片段 ((beginIndex == 0) & (endIndex == count)) ?this : new String(offset + beginIndex, endIndex - beginIndex, value);
1赞 Praveen Kumar 4/19/2018
正在创建正确的新字符串对象,但如果您看到它正在传递值,该值是原始字符串的 char 数组,而新创建的字符串将保留对完整 char 数组的引用。您可以比较 Java 6 和 8 的实现,Java 7 和 8 使用 Arrays.copyOfRange(value, offset, offset+count) 返回实际的子字符串
0赞 Karan Khanna 4/19/2018
明白了。谢谢。
0赞 Peter Mortensen 5/13/2021
“实时”是什么意思?
14赞 6 revsMarko Pacak #51

造成潜在巨大内存泄漏的另一种方法是保留对 .Map.Entry<K,V>TreeMap

很难评估为什么这仅适用于 s,但通过查看实现,原因可能是:a 存储对其同级的引用,因此,如果 a 已准备好被收集,但其他类保留了对其任何一个的引用,那么整个 Map 将保留到内存中。TreeMapTreeMap.EntryTreeMapMap.Entry


现实生活场景:

想象一下,有一个返回大数据结构的数据库查询。人们通常使用 s 作为元素插入顺序的保留。TreeMapTreeMap

public static Map<String, Integer> pseudoQueryDatabase();

如果查询被调用了很多次,并且对于每个查询(因此,对于每个返回的查询),您保存了一个位置,则内存将不断增长。MapEntry

请考虑以下包装类:

class EntryHolder {
    Map.Entry<String, Integer> entry;

    EntryHolder(Map.Entry<String, Integer> entry) {
        this.entry = entry;
    }
}

应用:

public class LeakTest {

    private final List<EntryHolder> holdersCache = new ArrayList<>();
    private static final int MAP_SIZE = 100_000;

    public void run() {
        // create 500 entries each holding a reference to an Entry of a TreeMap
        IntStream.range(0, 500).forEach(value -> {
            // create map
            final Map<String, Integer> map = pseudoQueryDatabase();

            final int index = new Random().nextInt(MAP_SIZE);

            // get random entry from map
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue().equals(index)) {
                    holdersCache.add(new EntryHolder(entry));
                    break;
                }
            }
            // to observe behavior in visualvm
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public static Map<String, Integer> pseudoQueryDatabase() {
        final Map<String, Integer> map = new TreeMap<>();
        IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
        return map;
    }

    public static void main(String[] args) throws Exception {
        new LeakTest().run();
    }
}

每次调用后,实例都应该准备好进行收集,但这不会发生,因为至少有一个实例存储在其他位置。pseudoQueryDatabase()mapEntry

根据您的设置,应用程序可能会在早期阶段崩溃,因为 .jvmOutOfMemoryError

从这张图中可以看出内存是如何不断增长的。visualvm

Memory dump - TreeMap

散列数据结构 () 不会发生同样的情况。HashMap

这是使用 .HashMap

Memory dump - HashMap

解决方案是什么?只需直接保存键/值(您可能已经这样做了),而不是保存 .Map.Entry


在这里写了一个更广泛的基准测试。

10赞 2 revs, 2 users 78%Pavel Molchanov #52

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

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

执行此操作的最简单方法是使用 JVM 附带的 jstat 实用程序:

jstat -gcutil <process_id> <timeout>

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

一旦您发现完全垃圾回收执行得太频繁并且花费了太多时间,您就可以认为应用程序正在泄漏内存。

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

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

然后,您需要使用内存分析器(例如Eclipse内存分析器(MAT))分析堆.bin文件。

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

3赞 3 revs, 2 users 85%Hearen #53

内存泄漏是一种资源泄漏,当计算机程序错误地管理内存分配时,就会发生这种泄漏,以至于不再需要的内存不会被释放=>维基百科的定义

这是一个相对基于上下文的主题,你可以根据自己的喜好创建一个,只要未使用的引用永远不会被客户使用,但仍然保持活力。

第一个示例应该是一个自定义堆栈,而不会在 Effective Java 第 6 项清空过时的引用。

当然,只要你愿意,还有更多,但如果我们只看一下 Java 内置类,它可能会有一些

subList()

让我们检查一些超级愚蠢的代码来产生泄漏。

public class MemoryLeak {
    private static final int HUGE_SIZE = 10_000;

    public static void main(String... args) {
        letsLeakNow();
    }

    private static void letsLeakNow() {
        Map<Integer, Object> leakMap = new HashMap<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            leakMap.put(i * 2, getListWithRandomNumber());
        }
    }



    private static List<Integer> getListWithRandomNumber() {
        List<Integer> originalHugeIntList = new ArrayList<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            originalHugeIntList.add(new Random().nextInt());
        }
        return originalHugeIntList.subList(0, 1);
    }
}

实际上,还有另一个技巧,我们可以利用HashMap的查找过程来导致内存泄漏。实际上有两种类型:

  • hashCode()总是一样的,但又是不同的;equals()
  • 使用 random 和 always true;hashCode()equals()

为什么?

hashCode() -> bucket => equals() 来定位货币对


我打算先提到substring(),然后再提到,但似乎这个问题已经得到解决,因为它的源代码出现在 JDK 8 中。subList()

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
0赞 3 revs, 2 users 76%Amir Fo #54

Java 内存泄漏示例之一是 MySQL 的内存泄漏错误,当忘记调用 ResultSets close 方法时。例如:

while(true) {
    ResultSet rs = database.select(query);
    ...
    // going to next step of loop and leaving resultset without calling rs.close();
}
2赞 3 revsSapphire_Brick #55

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();
        }
    }
}

评论

0赞 Peter Mortensen 5/13/2021
解释是有序的。例如,要点/想法是什么?它与以前的答案有何不同?
0赞 Jessie Lesbian #56

创建一个仅包含 while-true 循环的 JNI 函数,并使用另一个线程中的大对象调用它。GC 不太喜欢 JNI,并且会将对象永远保留在内存中。

15赞 6 revs, 3 users 71%Tashkhisi #57

Java 中有很多内存泄漏的好例子,我将在这个答案中提到其中的两个。

示例 1:

下面是 Effective Java, Third Edition 一书中内存泄漏的一个很好的示例(第 7 项:消除过时的对象引用):

// Can you spot the "memory leak"?
public class Stack {
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private Object[] elements;
    private int size = 0;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        return elements[--size];
    }

    /*** Ensure space for at least one more element, roughly* doubling the capacity each time the array needs to grow.*/
    private void ensureCapacity() {
        if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

本书的这段话描述了为什么这种实现会导致内存泄漏:

如果堆栈增长然后收缩,则从 堆栈不会被垃圾回收,即使程序使用 堆栈不再引用它们。这是因为 堆栈维护对这些对象的过时引用。一个过时的 引用只是一个永远不会被取消引用的引用 再。在这种情况下,任何超出“活动部分”的引用 元素数组已过时。活动部分包括 索引小于 size 的元素

以下是本书解决此内存泄漏的解决方案:

解决此类问题的方法很简单:空出 一旦它们过时,引用。对于我们的 Stack 类, 对某个项目的引用在弹出后立即过时 离开堆栈。pop 方法的更正版本如下所示:

public Object pop() {
    if (size == 0) throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // Eliminate obsolete reference
    return result;
}

但是,我们如何防止内存泄漏的发生呢?这是书中的一个很好的警告:

一般来说,每当一个类管理自己的内存时, 程序员应警惕内存泄漏。每当一个元素 ,则元素中包含的任何对象引用都应 无效。

示例 2:

观察者模式也可能导致内存泄漏。您可以在以下链接中阅读有关此模式的信息:观察者模式

这是 Observer 模式的一种实现:

class EventSource {
    public interface Observer {
        void update(String event);
    }

    private final List<Observer> observers = new ArrayList<>();

    private void notifyObservers(String event) {
        observers.forEach(observer -> observer.update(event)); //alternative lambda expression: observers.forEach(Observer::update);
    }

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void scanSystemIn() {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            notifyObservers(line);
        }
    }
}

在此实现中,在 Observer 设计模式中是 Observable 的,可以保存指向对象的链接,但此链接永远不会从 中的字段中删除。因此,它们永远不会被垃圾收集器收集。解决此问题的一种解决方案是向客户端提供另一种方法,以便在不再需要上述观察者时从现场删除这些观察者:EventSourceObserverobserversEventSourceobservers

public void removeObserver(Observer observer) {
    observers.remove(observer);
}
-2赞 Hoang TO #58

对以前的答案(更快地生成内存泄漏)的一点改进是使用从大型 XML 文件加载的 DOM 文档实例。

0赞 2 revs, 2 users 71%degr #59

这很简单:

Object[] o = new Object[]{};
while(true) {
    o = new Object[]{o};
}

评论

0赞 Boann 12/24/2022
不是内存泄漏。整个物品堆栈仍然是可收集的。
-1赞 TheWaterWave222 #60

您可以尝试让许多缓冲的读取器尝试使用条件从不为 false 的循环同时打开同一文件。最重要的是,这些永远不会关闭。while

1赞 3 revsBoann #61

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

我有一个 GUI 应用程序,它显示多个选项卡式文档。关闭文档后,如果在选项卡上的任何组件上使用右键单击上下文菜单,则该文档会滞留在内存中。菜单在选项卡之间共享,结果发现,在调用 popupMenu.show(Component invoker, int x, int y) 后,该组件会悄悄地作为菜单的“调用程序”保留下来,直到它被 更改或清除。间接地,调用程序引用保留了整个文档以及与之关联的所有内容。setInvoker(null)

值得注意的是,菜单只能以这种方式保存对旧组件的一个引用,因此这种内存泄漏不会无限制地增长。