我有一个系统,其中许多线程生成要插入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对象。
添加代码段
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
这是我的代码的错误吗?
请指导。
答案 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指导我正确的方向