我正在使用Jetty 9.3.5.v20151012通过websocket将大量事件传递给客户端。事件由3个部分组成:数字,事件类型和时间戳,每个事件被序列化为byte []并使用ByteBuffer发送。

经过一定小时/天的时间后,根据客户端的数量,我注意到堆内存增加了,GC也没有恢复它的可能性。
当堆(设置为512MB)几乎已满时,jvm使用的内存大约为700-800 MB,而CPU则为100%(它似乎像GC经常尝试清理的接缝)。一开始,当我启动Jetty时,调用GC时内存大约为30MB,但一段时间后,这个数字越来越多。最终,该过程被终止。

我正在将jvisualvm用作内存泄漏调试的探查器,并附加了head dump的一些屏幕截图:

java -  jetty -使用websockets和ByteBuffer时可能发生内存泄漏-LMLPHP
java -  jetty -使用websockets和ByteBuffer时可能发生内存泄漏-LMLPHP
java -  jetty -使用websockets和ByteBuffer时可能发生内存泄漏-LMLPHP

这是处理使用ByteBuffer发送消息的主要代码:

我基本上有一个方法可以为需要在一条消息中发送的所有事件创建一个byte [](全字节):

byte[] numberBytes = ByteBuffer.allocate(4).putFloat(number).array();
byte[] eventBytes = ByteBuffer.allocate(2).putShort(event).array();
byte[] timestampBytes  = ByteBuffer.allocate(8).putDouble(timestamp).array();

for (int i = 0; i < eventBytes.length; i++) {
    fullbytes[i + scount*eventLength] = eventBytes[i];
}

for (int i = 0; i < numberBytes.length; i++) {
    fullbytes[eventBytes.length + i + scount*eventLength] = numberBytes[i];
}

for (int i = 0; i < timestampBytes.length; i++) {
    fullbytes[numberBytes.length + eventBytes.length + i + scount*eventLength] = timestampBytes[i];
}


然后另一种方法(在单独的线程中调用)在websockets上发送字节

ByteBuffer bb = ByteBuffer.wrap(fullbytes);
wsSession.getRemote().sendBytesByFuture(bb);
bb.clear();


正如我在一些地方(在文档或herehere中)所读的,由于我没有使用直接ByteBuffer,因此不应出现此问题。这可能是与Jetty / websockets相关的错误吗?

请指教!

编辑:

我进行了更多测试,并且注意到将消息发送到未连接的客户端时出现问题,但是码头未收到onClose事件(例如,用户将笔记本电脑置于待机状态)。由于不会触发on close事件,因此服务器代码不会注销客户端,而是继续尝试将消息发送到该客户端。我不知道为什么,但是在1或2个小时后收到关闭事件。另外,有时(在未知情况下)尽管已接收到事件并且未注册客户端(套接字),但仍挂起了对WebSocketSession对象的引用(针对该客户端)。我还没有找到原因。

在此之前,我有2种可能的解决方法,但我不知道如何实现(还有其他良好的用途):


始终检测连接何时未打开(或暂时关闭,例如,用户将笔记本电脑置于待机状态)。我尝试使用sendPing()和实现onFrame(),但找不到解决方案。有没有办法做到这一点?
定期“刷新”缓冲区。如何丢弃未发送给客户端的消息,以使它们不继续排队?


编辑2

这可能将主题指向了另一个方向,因此我发表了另一篇文章here

编辑3

我已经对发送的大量消息/字节进行了更多的测试,我发现了为什么“缝”有时仅出现内存泄漏:在与sevlet.configure()不同的线程上异步发送字节时)被调用,经过大量的构建后,客户端断开连接后不会释放内存。另外,当使用sendBytes(ByteBuffer)时,仅使用sendBytesByFuture(ByteBuffer)和sendBytes(ByteBuffer,WriteCallback)时,我无法模拟内存泄漏。

这似乎很奇怪,但是我不认为我在测试中做错了什么。

码:

@Override
public void configure(WebSocketServletFactory factory) {
    factory.getPolicy().setIdleTimeout(1000 * 0);
    factory.setCreator(new WebSocketCreator() {

    @Override
    public Object createWebSocket(ServletUpgradeRequest req,
            ServletUpgradeResponse resp) {
        return new WSTestMemHandler();
    }
});
}

@WebSocket
public class WSTestMemHandler {
    private boolean connected = false;
    private int n = 0;

    public WSTestMemHandler(){
    }

    @OnWebSocketClose
    public void onClose(int statusCode, String reason) {
        connected = false;
        connections --;
        //print debug
    }

    @OnWebSocketError
    public void onError(Throwable t) {
    //print debug
    }

    @OnWebSocketConnect
    public void onConnect(final Session session) throws InterruptedException {

        connected = true;
        connections ++;
    //print debug

        //the code running in another thread will trigger memory leak
        //when to client endpoint is down and messages are still sent
        //because the GC will not cleanup after onclose received and
        //client disconnects

        //if the "while" loop is run in the same thread, the memory
        //can be released when onclose is received, but that would
        //mean to hold the onConnect() method and not return. I think
        //this would be bad practice.

        new Thread(new Runnable() {

            @Override
            public void run() {

                while (connected) {

                    testBytesSend(session);
                    try {
                        Thread.sleep(4);
                    } catch (InterruptedException e) {
                    }

                }
                //print debug
            }
        }).start();


    }



    private void testBytesSend(Session session) {

        try {
            int noEntries = 200;
            ByteBuffer bb = ByteBuffer.allocate(noEntries * 14);
            for (int i = 0; i < noEntries; i++) {
                n+= 1.0f;
                bb.putFloat(n);
                bb.putShort((short)1);
                bb.putDouble(123456789123.0);
            }
            bb.flip();


            session.getRemote().sendBytes(bb, new WriteCallback() {

                @Override
                public void writeSuccess() {

                }

                @Override
                public void writeFailed(Throwable arg0) {

                }
            });


        //print debug
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

最佳答案

您的ByteBuffer使用效率极低。

不要仅仅为了获取字节数组而创建所有次要/微小的ByteBuffer,然后将其扔掉。 ick


  注意:您甚至没有正确使用.array()调用,因为并非所有ByteBuffer分配都具有可以访问的后备数组。


字节数组的numberByteseventBytestimestampBytesfullbytes不应该存在。

创建单个ByteBuffer,代表您打算发送的整个邮件,然后将其分配为所需的大小或更大的大小。

然后将所需的单个字节放入其中,翻转它,并为Jetty实现提供单个ByteBuffer

Jetty将使用标准的ByteBuffer信息(例如positionlimit)来确定应实际发送该ByteBuffer的哪一部分。

关于java - jetty -使用websockets和ByteBuffer时可能发生内存泄漏,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34578235/

10-10 18:06
查看更多