我使用Jetty 9.3.5.v20151012使用websockets向客户端提供大量事件。事件由3部分组成:数字,事件类型和时间戳,每个事件序列化为byte []并使用ByteBuffer发送。
经过一定的小时数/天后,根据客户端数量的不同,我注意到堆内存增加,GC无法恢复。 当堆(设置为512MB)几乎已满时,jvm使用的内存大约为700-800 MB,CPU为100%(它的接口就像GC经常尝试清理一样)。一开始,当我启动Jetty时,调用GC时内存大约为30MB,但过了一段时间后,这个数字会越来越多。最终这个过程被杀死了。
我使用jvisualvm作为内存泄漏调试的分析器,并附上了头部转储的一些截图:
以下是使用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();
由于我已经在一些地方(在文档或here和here中)阅读,因此我不应该出现此问题,因为我没有使用直接的ByteBuffer。这可能是与Jetty / websockets相关的错误吗?
请指教!
修改
我已经做了一些测试,我注意到在向未连接的客户端发送消息时出现了问题,但是jetty没有收到onClose事件(例如,用户将他的笔记本电脑置于待机状态)。由于未触发on close事件,因此服务器代码不会注销客户端并继续尝试将消息发送到该客户端。我不知道为什么会在1或2小时后收到关闭事件。此外,有时(虽然不知道上下文)虽然接收到事件并且客户端(套接字)未注册,但对WebSocketSession对象(对于该客户端)的引用仍然挂起。我还没有发现为什么会发生这种情况。
在此之前,我有2种可能的解决方法,但我不知道如何实现它们(它们还有其他好的用途):
编辑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();
}
}
}
答案 0 :(得分:1)
ByteBuffer
使用效率非常低。
不要创建所有那些次要/小的ByteBuffers只是为了得到一个字节数组,然后把它扔掉。 ICK。
注意:您甚至没有正确使用
.array()
调用,因为并非所有ByteBuffer
分配都有可以像这样访问的支持数组。
字节数组的numberBytes
,eventBytes
,timestampBytes
和fullbytes
不应该存在。
创建一个ByteBuffer
,表示您要发送的整个邮件,将其分配为您需要的大小或更大。
然后将您想要的单个字节放入其中,翻转它,并为Jetty实现单ByteBuffer
。
Jetty将使用标准ByteBuffer
信息(例如position
和limit
)来确定实际发送ByteBuffer
的哪一部分。