【问题标题】:Java Memory Leak - find all objects of class X not referenced by class YJava Memory Leak - 查找类 Y 未引用的所有类 X 的对象
【发布时间】:2014-03-08 04:02:04
【问题描述】:

我在 Java 中存在内存泄漏,其中我的堆转储中有 9600 个ImapClients,而只有 7800 个MonitoringTasks。这是一个问题,因为每个ImapClient 都应该归一个MonitoringTask 所有,所以那些额外的1800 个ImapClients 被泄露了。

一个问题是我无法在堆转储中隔离它们并查看是什么让它们保持活力。到目前为止,我只能通过使用外部证据来猜测ImapClients 悬挂在哪个位置上。我正在学习 OQL,我相信它可以解决这个问题,但它进展缓慢,而且我需要一段时间才能理解如何用新的查询语言执行这样的递归。

确定是否存在泄漏很困难,所以这是我的全部情况:

  • 这个过程在一周前就已经产生了 OOME。我以为我已经修复了它,我正在尝试验证我的修复是否正常工作,而无需再等一整周来查看它是否再次喷出 OOME。
  • 此任务在启动时创建 7000-9000 ImapClients,然后在正常操作下连接和断开其中的极少数。
  • 我检查了另一个运行旧的预 OOME 代码的进程,它显示的数字是 9000/9100 而不是 7800/9600。我不知道为什么旧代码与新代码不同,但这是泄漏的证据。

这个问题的重点是我可以确定是否存在泄漏。有一个业务规则,每个ImapClient 都应该是MonitoringTask 的裁判。如果我要问的这个查询是空的,那么就没有泄漏。如果它提出了对象,连同这个业务规则,它不仅是泄漏的证据,而且是确凿的证据。

【问题讨论】:

  • 有多少并发 ImapClient 活着?你能限制 ImapClient 对象的数量并重用它们吗?
  • @javaseeker 我绝对不能这样做。它们需要消失并被清理干净。
  • 你确定ImapClient和MonitoringTask是分配在同一个堆类的,所以会一起收集吗?除了在 MonitoringTasks 中,您还会在哪里存储指向 ImapClients 的指针——如果它们真的被“泄露”(在这种略微扭曲的意义上),那是因为它们是从其他对象(可能是其他 ImapClients)引用的。
  • 除了 MonitoringTask 之外,您是否曾经在任何其他对象中存储对 ImapClient 的引用?你说 ImapClient 是“更短暂的”——这是怎么发生的? ImapClient 引用是否设置为null?您是否为单个 MonitoringTask 重复分配 ImapClients?
  • 如果您以高得多的速度创建 ImapClients,则很可能在任何时间点都会有更多的“已死但未收集”。

标签: java memory-leaks visualvm


【解决方案1】:

检查您的代码是否有终结器,尤其是与 IMapclient 相关的任何内容。

可能是您的 MonitoringTasks 很容易被收集,而您的 IMapclient 已完成,因此在终结器线程运行之前一直停留在堆上(尽管已死)。

【讨论】:

    【解决方案2】:

    您的预期不正确,没有实际证据表明发生任何泄漏

    垃圾收集器的目标是在需要时释放空间,并且 只有这样,其他任何事情都是浪费资源。绝对有 试图保留尽可能多的可用空间没有任何好处 一直可用,只有缺点。

    仅仅因为某物是垃圾回收的候选者并不会 意味着它实际上会被收集,并且没有办法 强制垃圾回收。

    我在任何地方都没有看到任何提及OutOfMemoryError

    你所关心的你无法控制,反正也不能直接控制

    您应该关注的是在您的控制范围内的内容,即确保您不会在需要的时间内持有参考资料,并且您不会不必要地重复某些内容。 Java 中的垃圾收集例程经过高度优化,如果您了解它们的算法是如何工作的,就可以确保您的程序以最佳方式运行以使这些算法正常工作。

    Java 堆内存不像其他语言中的手动管理内存,这些规则不适用

    在其他语言中被认为是内存泄漏的东西与 Java 中的垃圾回收系统不同。

    在 Java 中,内存很可能不会被一个正在泄漏的 uber-object 消耗(其他环境中的悬空引用)。

    由于中间对象所处的范围以及在运行时可能会发生变化的许多其他因素,中间对象的保留时间可能比垃圾收集器预期的要长。

    示例: 垃圾收集器可能会确定有候选者,但因为它认为仍有大量内存可用,因此在时间上清除它们可能过于昂贵时间点,等到内存压力变大。

    垃圾收集器现在真的很好,但它不是魔法,如果你在做退化的事情,它会导致它不能最佳地工作。互联网上有很多关于所有 JVM 版本的垃圾收集器设置的文档。

    这些未引用的对象可能还没有达到垃圾收集器认为需要它们才能从内存中清除的时间,或者可能有其他对象(List)持有对它们的引用您没有意识到的示例仍然指向该对象。这就是 Java 中最常被称为 leak 的东西,更具体地讲是引用泄漏。

    我没有看到任何提及OutOfMemoryError

    您的代码可能没有问题,垃圾收集系统可能没有承受足够的压力来启动和释放您认为应该清理的对象。 您认为可能不是问题,除非您的程序因OutOfMemoryError 而崩溃。这不是 C、C++、Objective-C 或任何其他手动内存管理语言/运行时。您无法以您期望的详细程度来决定内存中的内容。

    【讨论】:

    • 我有一个运行旧代码的进程,显示数字为 9000/9100 而不是 7800/9600。这是泄漏的进一步证据。此过程还会在启动时创建 9000 个,然后在几天内相对缓慢地循环它们。对我来说,它看起来更像是泄密,虽然可能需要一到两周才能 OOME,所以我需要在 OOME 之前提供泄密的证据。
    • 除非你得到OutOfMemoryError,否则这不是泄漏,简单明了,GC 不需要收集它们,这是正确的。再读一遍我的帖子,如果你没有做任何堕落的事情,你就完成了,否则再读一遍,直到你意识到发生了什么。
    • 这就是这个问题的重点。我试图确定为什么它们没有被垃圾收集。也许我还应该提一下,截至上周,我的 OOME 不断涌现,并进行了我认为可以修复的代码更改。我正在尝试确定我的修复是否有效,而没有给该过程另外 2 周的时间来抛出另一个 OOME。
    • 因为 GC 不需要收集它们,简单明了,您可以或需要做的事情几乎没有。
    • 更新了我的问题。 OOME 是获得泄漏证据的唯一方法是错误的。
    【解决方案3】:

    显而易见的答案是在你的代码中添加一个WeakHashMap<X, Object>(和Y)——一个跟踪X的所有实例,另一个跟踪Y的所有实例(使它们成为类的静态成员并将每个对象插入构造函数中具有空“值”的映射)。然后,您可以随时遍历这些映射以查找 X 和 Y 的所有活动实例,并查看哪些 X 未被 Y 引用。您可能希望首先触发完整的 GC,以忽略已死且尚未收集的对象。

    【讨论】:

    • WeakHashMap 有弱键,而不是弱值,所以这不会做你想做的事。您可以使用 Apache 的 WeakValueHashMap 或 Guava 的 MapMaker().weakKeys().makeMap() 来获取具有弱值的地图。
    • 那就更简单了——将 X/Y 设为键并忽略值。我对 java 奇怪的反向命名感到困惑。
    • 这当然是不完美的,因为你不能完全锁定等待你的gc发生的整个过程。
    • 你也不能触发一个完整的GC,这是不可能的,它只是一个在大多数情况下被忽略的提示
    • @JarrodRoberson: System.gc() 将在大多数虚拟机上执行此操作,而在那些通常不会执行此操作的虚拟机上(可能需要调用 System.gc() 然后暂停所有用户线程一段时间,在最坏的情况下)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多