ConcurrentLinkedQueue $ Node对象中的内存泄漏

时间:2014-11-22 14:21:57

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

我有一个系统,其中许多线程生成要插入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),同时分析观察到ConcurrentLinkedQueue $ Node对象的实例超过15,000,00个。在检查实例视图时,我可以看到LinkedList节点值(属性" item")及其对下一个节点的引用(属性" next")对于大多数对象设置为null 。意味着这些Node对象是垃圾收集的候选对象,但它没有发生,并且在内存中堆积了解除引用的Node对象。 enter image description here

添加代码段

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的错误是废弃的节点永远不会被垃圾收集吗?

OR

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

OR

这是我的代码的错误吗?

请指导。

2 个答案:

答案 0 :(得分:3)

  

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

不,这些是外向参考。相反,您应该检查对这些对象的传入引用。有些东西抓住了它们。

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

通常,您必须分析GC根的路径,以找到保留在对象上的内容。

CLQ使这个问题变得复杂,因为它懒惰地更新/清除了一些在并发访问下可能会失败的指针,但是应该在以后清理它们,即它们不应该继续堆积。

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

答案 1 :(得分:2)

正如8472指出的那样,对转储进行了分析,发现它与ConcurrentLinkedQueue的poll()和offer()方法不同。

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

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

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

我们监控系统大约一个月,内存占用量一致。感谢8472指导我正确的方向