Я использую Jetty 9.3.5.v20151012 для доставки большого количества событий клиентам с использованием веб-карт. События состоят из трех частей: числа, типа события и временной метки, а каждое событие сериализуется как байт [] и отправляется с использованием ByteBuffer.Jetty - Возможная утечка памяти при использовании веб-узлов и ByteBuffer
После определенного количества часов/дней, в зависимости от количества клиентов, я замечаю увеличение объема памяти кучи и не дает возможности GC восстановить его. Когда куча (установленная на 512 МБ) почти заполнена, память, используемая jvm, составляет около 700-800 МБ, а ЦП составляет 100% (это похоже на то, что GC очень часто пытается очистить). В начале, когда я запускаю Jetty, при вызове GC память составляет около 30 МБ, но через некоторое время это число увеличивается все больше и больше. В конце концов процесс убит.
Я использую jvisualvm, как профайлер для отладки утечек памяти, и я приложил некоторые скриншоты из головы отвала:
Вот основной код, который обрабатывает отправки сообщений используя ByteBuffer:
У меня в основном есть метод, который создает байт [] (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?
Обратите внимание!
EDIT:
Я сделал еще несколько тестов, и я заметил, что проблема возникает при отправке сообщений клиента, который не подключен, но молы не получили OnClose события (напр. пользователь переносит свой ноутбук в режим ожидания). Поскольку событие on close не запускается, код сервера не отменяет регистрацию клиента и продолжает пытаться отправить сообщения этому клиенту. Я не знаю, почему, но закрытие происходит через 1 или 2 часа. Кроме того, иногда (пока еще не знакомы с контекстом), хотя событие получено и клиент (сокет) не зарегистрирован, ссылка на объект WebSocketSession (для этого клиента) все еще зависает. Я не узнал, почему это происходит.
До тех пор, у меня есть 2 возможных путей их устранения, но я понятия не имею, как достичь их (то есть и другие хорошие использования, а):
- Всегда обнаружить, когда соединение не открыто (или временно закрыт , например, пользователь переносит ноутбук в режим ожидания). Я попытался использовать sendPing() и реализовать onFrame(), но я не смог найти решение. Есть ли способ сделать это?
- Периодически «промывать» буфер. Как я могу отказаться от сообщений, которые не были отправлены клиенту, чтобы они не останавливались в очереди?
EDIT 2
Это может указывать на тему в другую сторону, поэтому я сделал еще один пост here.
EDIT 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();
}
}
}
Как вы определяете, что «без возможности для GC восстановить его»? Вы получаете OutOfMemoryError? – Kayaman
Привет, я редактировал свой пост. –
Когда куча (установленная на 512 МБ) почти заполнена, память, используемая jvm, составляет около 700-800 МБ, а ЦП - 100% (это похоже на то, что GC очень часто пытается очистить). В начале, когда я запускаю Jetty, при вызове GC память составляет около 30 МБ, но через некоторое время это число увеличивается все больше и больше. В конце концов процесс убит. Я не дождался, пока он полностью выйдет из памяти, потому что процессор находится на 100%, и сервер становится непригодным; И я не могу шов, чтобы воспроизвести проблему в процессе развития. –