Jetty - 使用websockets和ByteBuffer

时间:2016-01-03 15:33:18

标签: java memory-leaks websocket jetty bytebuffer

我使用Jetty 9.3.5.v20151012使用websockets向客户端提供大量事件。事件由3部分组成:数字,事件类型和时间戳,每个事件序列化为byte []并使用ByteBuffer发送。

经过一定的小时数/天后,根据客户端数量的不同,我注意到堆内存增加,GC无法恢复。 当堆(设置为512MB)几乎已满时,jvm使用的内存大约为700-800 MB,CPU为100%(它的接口就像GC经常尝试清理一样)。一开始,当我启动Jetty时,调用GC时内存大约为30MB,但过了一段时间后,这个数字会越来越多。最终这个过程被杀死了。

我使用jvisualvm作为内存泄漏调试的分析器,并附上了头部转储的一些截图:

enter image description here enter image description here enter image description here

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

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

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相关的错误吗?

请指教!

修改

我已经做了一些测试,我注意到在向未连接的客户端发送消息时出现了问题,但是jetty没有收到onClose事件(例如,用户将他的笔记本电脑置于待机状态)。由于未触发on close事件,因此服务器代码不会注销客户端并继续尝试将消息发送到该客户端。我不知道为什么会在1或2小时后收到关闭事件。此外,有时(虽然不知道上下文)虽然接收到事件并且客户端(套接字)未注册,但对WebSocketSession对象(对于该客户端)的引用仍然挂起。我还没有发现为什么会发生这种情况。

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

  1. 始终检测连接何时未打开(或临时关闭,例如用户将笔记本电脑置于待机状态)。我尝试使用sendPing()并实现onFrame(),但我找不到解决方案。有没有办法做到这一点?
  2. 定期&#34;冲洗&#34;缓冲区。如何丢弃未发送给客户的邮件,以便他们不会继续排队?
  3. 编辑2

    这可能会将主题指向另一个方向,因此我又发了一篇帖子here

    编辑3

    我已经针对发送的大量消息/字节做了更多测试,我发现了为什么&#34;它接合了#34;有时只出现内存泄漏:当在调用sevlet.configure()时使用的不同线程上发送字节异步时,在大量构建之后,在客户端断开连接后内存不会被释放。使用sendBytes(ByteBuffer)时,我也无法模拟内存泄漏,只能使用sendBytesByFuture(ByteBuffer)和sendBytes(ByteBuffer,WriteCallback)。

    这种缝很奇怪,但我不相信我做某事&#34;错误&#34;在测试中。

    代码:

    @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();
            }
        }
    
    
    }   
    

1 个答案:

答案 0 :(得分:1)

ByteBuffer使用效率非常低。

不要创建所有那些次要/小的ByteBuffers只是为了得到一个字节数组,然后把它扔掉。 ICK。

  

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

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

创建一个ByteBuffer,表示您要发送的整个邮件,将其分配为您需要的大小或更大。

然后将您想要的单个字节放入其中,翻转它,并为Jetty实现单ByteBuffer

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