Java内存溢出堆空间错误:如何从向量创建deque?

4

我是Java的新手,非常需要你的帮助。

目前我正在使用一个队列,一个接收线程将数据放入这个队列中,而解析器从中读取数据。但问题是,接收器可能以惊人的速度接收数据,例如每秒3000次,而解析器只能以每秒100次的速度进行解析。

编辑:我已经检查过了,队列首先停留在100左右,十秒钟后开始以每秒100个的速度增长,并在2000左右崩溃。可能存在内存泄漏吗?

我的代码(在一个紧密的循环中)是:

byte[] data = new byte[1024];
System.arraycopy(udpPacket.getData(), 0, data, 0, 1024);
queue.offer(data);

堆内存被快速填满,导致出现内存溢出异常。我猜问题在于队列是使用链表实现的,所有指针都必须保存在堆中。
我知道有一个使用缓冲区实现相同功能的C版本,但由于部署问题,我们只能使用Java。

1
由于讨论仍在进行中,您可以指定接收或流传输的数据量,无论是使用TCP还是UDP以及传输频率是多少。这些信息对于建议最佳实践是必要的。 - stacker
1
看起来你在做很多复制。你可能想把你的数据包装成输入流,然后传递它们。 - Nick Hristov
@stacker:我无法控制输入,只能控制侦听器的采样。@Nick:是的,我正在进行大量复制,并且我不理解将数据包装为输入流。您能否请解释一下输入流如何以及为什么更好? - TiansHUo
7个回答

2
如果你每秒收到 3000 条数据,但只能处理 100 条/秒,迟早会耗尽内存。我建议你使用更多线程来进行解析。
关于队列,可以看一下 LinkedBlockingDequeLinkedBlockingQueue。它们都是高性能的线程安全队列实现。

谢谢您的快速回答,但我有一个愚蠢的问题,如何使用多个线程解析一个队列,我不理解。 另一个问题是,我的内存在几秒钟内就耗尽了,这完全不能接受... - TiansHUo
1
我认为这个idrosid意味着你需要将从队列中移除项目的操作与处理该项目的操作分离。因此,您可以从队列中读取项目并将其传递给多个不同的线程之一来进行解析。它允许您同时处理多个队列项。话虽如此,我认为它仍然只是推迟问题出现的时间,而不是消除问题。 - DaveH
为什么它只是推迟问题而不是消除它? - TiansHUo

2
由于数据传输速度比处理速度快30倍,如果内存耗尽前传输就完成了,您可以使用以下命令扩展HeapSize:java -Xms<initial heap size> -Xmx<maximum heap size>
  • 或者,如您所建议的,将数据转储到磁盘上并延迟处理。
  • 否则,您需要优化解析器。

是的,优化解析器是个好主意,因为当高峰发生时,数据通常是同质的,所以也许我可以保留顶部数据的统计信息并进行一些聚合。 - TiansHUo
@TiansHUo,这不是答案。现在可能可以解决您的问题,但当需要处理更多数据时,您可能会遇到更多问题... - bruno conde
哎?你能否澄清一下,当需要处理更多数据时会出现什么问题?或者你能指导如何改进算法吗? - TiansHUo
@TiansHUo,假设您有一个需要不断解析的无限数据源。增加内存大小并不能解决问题,因为生产者传输速率大于消费者的传输速率。我和@idrosid 建议使用BlockingDeque是解决这种"内存泄漏"的方案,因为它将有限的数据缓存在队列中,在达到限制时阻止生产者继续添加数据。 - bruno conde
你误解了问题,通常情况下,消费者速率比生产者速率更快,但有时生产者会出现峰值。也许将数据放在磁盘上是实现更大缓存的一种方式? - TiansHUo

1
如果生产者生产的数据量超过了消费者可处理的数据量,那么数据将开始积累并最终导致内存不足问题。这取决于(1)生产者和消费者之间的速率差异,以及(2)需要处理的数据量。
我建议您限制队列中的项目数。使用BlockingDeque -> LinkedBlockingDeque 来限制队列的容量,并在达到极限时阻塞您的循环。这样,队列就像解析器的缓存一样起作用。

但现在的问题是队列只有2000左右,我得到了一个内存溢出异常,我猜测某处存在内存泄漏。 - TiansHUo
哦,那真的是一小部分数据...从我的计算来看只有1.9MB...不应该有问题。 - bruno conde
有没有你可以建议的(最好是开源的)工具,可以帮助我看到真正的问题所在? - TiansHUo

1
我猜问题在于队列是使用链表制作的,所有指针都必须保存在堆中。
不这么认为。我认为真正的问题是您的系统获取输入的速率与处理它的速率不匹配。除非您能够以至少平均输入速率进行处理,否则最终会耗尽内存,无论您如何表示队列。
您必须改善处理速率,减少输入速率或丢弃数据。

0

我有一个理论,实现ArrayDeque(至少在Oracle JDK中,我不确定Android的情况)似乎从未真正释放弹出的元素。

换句话说,用于弹出元素的插槽只是被设置为空。而添加到尾部的元素将使“内部数组”无限增长,迟早会造成麻烦。

这段代码来自Oracle JDK 1.8.0_144:

public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];
    // Element is null if deque empty
    if (result == null)
        return null;
    elements[h] = null;     // Must null out slot
    head = (h + 1) & (elements.length - 1);
    return result;
}

这对我来说很麻烦。:(

如果我的分析是正确的,那么 ArrayDeque 似乎从来没有被设计成一个“真正”的 FIFO 队列,也不适合这样的目的。(不幸的是,我现在正需要这样的目的)

我正在调查 LinkedList(它也实现了 Deque)。


0

另一种方法是在队列变得过大时对数据进行抽样,并保存采样率,以便我们可以模拟原始数据。


0
当你运行java时,你可以使用-Xmx参数来为虚拟机提供更多的内存。例如,java -Xmx512m将允许虚拟机分配最多512Mb的内存。(默认值相对较小)。
但是,如果你在分配内存并用数据填充列表而从不删除它,无论你使用哪种语言,最终都会耗尽内存。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接