NioSocketChannel$WriteRequestQueue 导致内存溢出问题

4
我正在使用Netty来进行大文件上传。虽然一切正常,但是客户端使用的RAM似乎随着文件大小而增加。这不是预期的行为,因为所有内容都从读取源文件到写入目标文件都是通过管道传输的。
起初,我考虑过一种自适应缓冲区增长方式,直到达到Xmx,但是将Xmx设置为合理值(50M)后,很快就会出现OutOfMemoryError。
经过一些使用Eclipse Memory Analyzer的研究,发现保留堆内存的对象是:
org.jboss.netty.channel.socket.nio.NioSocketChannel$WriteRequestQueue

是否有设置此队列限制的选项,或者我需要使用ChannelFutures编写自己的队列来控制字节数并在达到限制时阻塞管道?

谢谢您的帮助,

祝好,

Renaud


3
你可以增加最大内存。如今1 GB已经不算多了。 - Peter Lawrey
不能将文件传输而不使用1GB RAM是桌面背景应用程序的一个选择。尽管这确实很痛苦,但这是不可选的。 - Renaud
2个回答

2

Netty Github上@normanmaurer的回答:

你应该使用

Channel.isWritable()

检查“队列”是否已满。如果是这样,您需要检查是否有足够的空间来写更多内容。如果您将数据写入得太快以致无法发送到客户端,则可能会发生这种情况。 当尝试通过DefaultFileRegion或ChunkedFile写入文件时,可以解决此类问题。


@normanmaurer谢谢,我错过了Channel的这个方法! 我想我需要读一下里面发生了什么:

org.jboss.netty.handler.stream.ChunkedWriteHandler

更新日期:2012/08/30 这是我为解决问题而编写的代码:

public class LimitedChannelSpeaker{
    Channel channel;
    final Object lock = new Object();
    long maxMemorySizeB;
    long size = 0;
    Map<ChannelBufferRef, Integer> buffer2readablebytes = new HashMap<ChannelBufferRef, Integer>();

    public LimitedChannelSpeaker(Channel channel, long maxMemorySizeB) {
        this.channel= channel;
        this.maxMemorySizeB = maxMemorySizeB;
    }

    public ChannelFuture speak(ChannelBuffer buff) {
        if (buff.readableBytes() > maxMemorySizeB) {
            throw new IndexOutOfBoundsException("The buffer is larger than the maximum allowed size of " + maxMemorySizeB + "B.");
        }
        synchronized (lock) {
            while (size + buff.readableBytes() > maxMemorySizeB) {
                try {
                    lock.wait();
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
            }
            ChannelBufferRef ref = new ChannelBufferRef(buff);
            ref.register();
            ChannelFuture future = channel.write(buff);
            future.addListener(new ChannelBufferRef(buff));
            return future;
        }
    }

    private void spoken(ChannelBufferRef ref) {
        synchronized (lock) {
            ref.unregister();
            lock.notifyAll();
        }
    }

    private class ChannelBufferRef implements ChannelFutureListener {

        int readableBytes;

        public ChannelBufferRef(ChannelBuffer buff) {
            readableBytes = buff.readableBytes();
        }

        public void unregister() {
            buffer2readablebytes.remove(this);
            size -= readableBytes;
        }

        public void register() {
            buffer2readablebytes.put(this, readableBytes);
            size += readableBytes;
        }

        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            spoken(this);
        }
    }
}

0
针对桌面背景应用程序,Netty是专为高度可扩展的服务器设计的,例如约10,000个连接。对于少于几百个连接的桌面应用程序,我会使用普通IO。您可能会发现代码更简单,并且它应该使用不到1 MB的内存。

代码已经存在,我们不会回滚到普通的IO实现。实现一个队列要便宜得多,在将每个项目写入通道时添加到队列中,并在WriteFuture完成时从队列中删除。 - Renaud
那可能是你最好的选择。Netty仍然会比普通IO解决方案使用更多的内存,但至少它会更有限制。 - Peter Lawrey
@NumRenaud,这样可以节省一些线程,但如果您在资源有限的计算机上运行桌面应用程序,您将使用多少连接?对于具有少量连接的最小内存占用,使用简单的阻塞代码将更加轻便。 - Peter Lawrey
我同意你的观点,但我的问题是:“如何在Netty中限制WriteQueue的大小”。我无法相信API中没有实现这个功能! - Renaud
2
@NumRenaud 在 Netty 的下一个主要版本中,您将能够指定您想要使用的队列实现。因此,在这里您可以使用一个有界队列。 - Norman Maurer
显示剩余5条评论

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