【问题标题】:Memory leak in ConcurrentLinkedQueue$Node objectsConcurrentLinkedQueue$Node 对象中的内存泄漏
【发布时间】:2014-11-22 14:21:56
【问题描述】:

我有一个系统,其中许多线程生成要插入到 NoSql 后端的日志。为了减少网络流量,我在服务器和后端之间引入了一个缓冲区。

环境是:

Java、JSP、Spring MVC、JDK 1.7 Apache-tomcat-6

使用的缓冲区是java中的ConcurrentLinkedQueue。还实现了一个 DBPushThread 以每 5 秒从队列中获取日志并将它们插入到后台。我们使用 offer() 进行插入,使用 poll() 进行弹出。根据 poll() - https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html#poll%28%29 的 javadoc,它将检索元素并更新队列的头部。所以这个节点永远不会被引用并最终被垃圾回收。

我运行了服务器 1 天,发现服务器随着时间的推移变得过于缓慢。使用 JVisualVM 对服务器进行堆转储(hprof),并在分析时观察到有超过 15,000,00 个 ConcurrentLinkedQueue$Node 对象实例。在检查实例视图时,我可以看到大多数对象的 LinkedList 节点值(属性“item”)及其对下一个节点(属性“next”)的引用设置为 null。意味着这些 Node 对象是垃圾收集的候选对象,但它没有发生并且取消引用的 Node 对象堆积在内存中。

加码sn-p

public void add(Log log) {
        buffer.offer(log);
    }

从队列中检索内容(这里总是将最大索引指定为队列大小)

public List<Log> getContents(int maxIndex) {
    List<Log> logs = new LinkedList<Log>();

    for (int i = 0; i < maxIndex; i++) {
        Log log = buffer.poll();
        logs.add(Log);
    }
    return logs;
}

我只将缓冲区(这是单例队列)作为实例变量。所有其他都是函数的本地范围。

这是 JDK 1.7 的一个错误,即废弃的节点永远不会被垃圾收集吗?

我需要在 ConcurrentLinkedQueue 中实现对象池吗?如果是这样,我该如何实现?

这是我的代码的错误吗?

请指导。

【问题讨论】:

  • 您应该准确说明“十万”的含义,或者对泄露的实例使用实际数字。我不得不用谷歌搜索this definition 并理解它的意思是 100,000(十万)。请允许 SO 读者之间存在文化差异。
  • 好的。我已经更正了。

标签: java memory-leaks jvm java.util.concurrent


【解决方案1】:

在检查实例视图时,我可以看到 LinkedList 节点值(属性“item”)及其对下一个节点的引用(属性“next”)对于大多数对象都设置为 null。

不,那些是传出引用。相反,您应该检查对这些对象的传入引用。有什么东西抓住了他们。

从您的屏幕截图中,它实际上看起来像是 CLQ 的头部和尾部都指向实例 #5,这让我想知道所有其他 Node 实例是由什么引用的。

通常,您必须分析 GC 根的路径,以找出对象持有的内容。

CLQ 使问题复杂化,因为它会延迟更新/清除一些在并发访问下可能会失败但应该稍后清理的指针,即它们不应该继续堆积。

您还应该检查您的堆转储分析器是否显示“浮动垃圾”,即符合收集条件但尚未收集的对象。如果是这种情况,你可能会找错树。

【讨论】:

  • 感谢 the8472 的输入。我已经更新了下面的解决方案。
【解决方案2】:

正如 8472 指出的那样,分析转储并观察到 ​​ConcurrentLinkedQueue 的 poll() 和 offer() 方法没有问题。

在我们的架构中,concurrentLinkedQueue 充当了日志堆积的缓冲区,DBPushThread 将从 CL Queue 中获取日志并将它们插入到后端存储中。使用的后端是弹性搜索。

由于弹性搜索的间歇性稳定性/扩展问题,DBPushThread 将日志插入到弹性搜索失败并引发异常。我们抛出了那个异常。因为是线程,所以会是 UnCaughtException 并且父线程永远不会得到通知。

很多日志被注入到 CL 队列,但没有从 CL 队列轮询(因为 DBPushThread 死了)。通过处理弹性搜索问题并在向弹性搜索插入数据时捕获异常,我们能够解决此问题。

我们对系统进行了大约 1 个月的监控,并且内存占用量是一致的。感谢 the8472 指引我正确的方向

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-31
    • 2018-06-13
    • 1970-01-01
    • 2011-03-08
    • 2012-09-12
    • 2015-05-12
    • 2017-06-20
    • 1970-01-01
    相关资源
    最近更新 更多